From 468743bfe75087ecb0160b85a0864fa81bcdded5 Mon Sep 17 00:00:00 2001 From: koka Date: Thu, 17 Sep 2026 03:10:39 +0200 Subject: [PATCH] Integrate verified Sanctuary beta.110 with clay workshop, eggs and seasonal snow --- CHANGELOG.md | 130 +++++ README.md | 165 ++++++- THIRD_PARTY_NOTICES.md | 10 + docs/backlog.md | 122 +++++ docs/catalogue-progression-beta099.md | 127 +++++ docs/clay-contrast-beta104.md | 41 ++ docs/clay-workshop-integration-beta110.md | 97 ++++ docs/collections-minecraft.md | 9 + docs/colored-bricks-beta102.md | 73 +++ docs/colored-bricks-order-beta103.md | 34 ++ docs/creative-construction-beta098.md | 74 +++ docs/expeditions-beta002.md | 5 + docs/exploration-familiers-beta100.md | 85 ++++ docs/flying-landing-beta101.md | 58 +++ docs/fourneau-allume-beta094.md | 79 +++ docs/living-hats-seasons-beta105.md | 143 ++++++ docs/metabli-beta096.md | 104 ++++ docs/metabli-construction.md | 185 +++++++ docs/packwiz.md | 123 +++++ docs/piston-shaft-beta095.md | 48 ++ docs/seasonal-snow-melt-beta109.md | 94 ++++ docs/snow-accumulation-beta108.md | 115 +++++ docs/spawn-eggs-acquisition-beta107.md | 107 ++++ docs/spawn-eggs-catalogue-beta107.md | 155 ++++++ docs/statuary-beta106.md | 172 +++++++ docs/temple-crash-beta097.md | 102 ++++ docs/tooltip-crash-beta093.md | 85 ++++ gradle.properties | 4 +- mods/sanctuary/build.gradle | 48 ++ .../client/Bricks102ClientChecks.java | 103 ++++ .../client/Catalogue099ClientChecks.java | 19 + .../sanctuary/client/Eggs107ClientChecks.java | 134 +++++ .../client/Exploration100ClientChecks.java | 123 +++++ .../client/Fourneau085ClientChecks.java | 48 +- .../client/Landing101ClientChecks.java | 81 +++ .../client/Living105ClientChecks.java | 86 ++++ .../sanctuary/client/Melt109ClientChecks.java | 105 ++++ .../client/Menus083ClientChecks.java | 6 +- .../client/Metatable096ClientChecks.java | 190 +++++++ .../client/Plans072ClientChecks.java | 60 ++- .../client/Schematics100ClientChecks.java | 82 +++ .../sanctuary/client/Snow108ClientChecks.java | 87 ++++ .../client/Statuary106ClientChecks.java | 158 ++++++ .../client/Temple097ClientChecks.java | 111 +++++ .../gametest/Tooltip093ClientChecks.java | 72 +++ .../sanctuary/gametest/Fourneau094Probe.java | 15 + .../sanctuary/gametest/Tooltip093Probe.java | 11 + .../mixin/Fourneau094ParticleProbeMixin.java | 17 + .../mixin/Tooltip093FontProbeMixin.java | 13 + .../resources/sanctuary-gametest.mixins.json | 2 + .../java/fr/koka/sanctuary/SanctuaryMod.java | 2 + .../sanctuary/building/ColoredBricks.java | 56 +++ .../sanctuary/client/ClayWorkshopScreen.java | 81 +++ .../sanctuary/client/CompanionTooltips.java | 23 +- .../koka/sanctuary/client/FourneauFire.java | 45 ++ .../koka/sanctuary/client/FourneauModels.java | 20 +- .../client/InhabitantCompanionWidget.java | 5 +- .../sanctuary/client/InlineAdvancements.java | 72 +++ .../sanctuary/client/MetatableClient.java | 37 ++ .../sanctuary/client/ModelImportErrors.java | 13 + .../sanctuary/client/MultiblockScreen.java | 9 + .../fr/koka/sanctuary/client/PlanClient.java | 131 ++++- .../fr/koka/sanctuary/client/PlanHistory.java | 45 ++ .../koka/sanctuary/client/PlanMaterials.java | 47 ++ .../sanctuary/client/PlanPreviewRenderer.java | 77 +++ .../koka/sanctuary/client/PlanThumbnail.java | 83 ++++ .../fr/koka/sanctuary/client/PlansScreen.java | 189 +++++-- .../sanctuary/client/ProgressionScreen.java | 22 +- .../client/SanctuaryKnowledgeClient.java | 2 + .../sanctuary/client/StatuaryRenderer.java | 80 +++ .../koka/sanctuary/client/StatuePalette.java | 24 +- .../client/panorama/PanoramaCapture.java | 9 +- .../sanctuary/companion/CompanionQueries.java | 4 +- .../sanctuary/cosmetics/FamiliarEntity.java | 73 +-- .../sanctuary/cosmetics/HeadBlockService.java | 17 +- .../sanctuary/cosmetics/HeadCosmeticData.java | 3 +- .../cosmetics/HeadCosmeticService.java | 1 + .../sanctuary/cosmetics/MobHeadActions.java | 114 +++++ .../sanctuary/cosmetics/MobHeadService.java | 14 +- .../koka/sanctuary/cosmetics/MobJournal.java | 48 ++ .../fr/koka/sanctuary/eggs/BirthEggs.java | 82 +++ .../expansion/ExpansionStructures23.java | 109 +++- .../expansion/ExpeditionTemples002.java | 23 +- .../expansion/NativeStructureMetadata23.java | 4 +- .../sanctuary/familiar/FamiliarBrain.java | 2 + .../koka/sanctuary/familiar/FamiliarRoam.java | 76 +++ .../sanctuary/mixin/BirthEggAllayMixin.java | 16 + .../sanctuary/mixin/BirthEggAnimalMixin.java | 15 + .../mixin/BirthEggSpecialAnimalMixin.java | 17 + .../sanctuary/mixin/BirthEggTurtleMixin.java | 15 + .../sanctuary/mixin/BirthEggUseMixin.java | 15 + .../mixin/BirthEggVillagerMixin.java | 27 + .../sanctuary/mixin/HeadSpawnerAccess.java | 6 + .../sanctuary/mixin/MobHeadGoalAccess.java | 7 + .../mixin/MobHeadLightningMixin.java | 12 + .../mixin/MobHeadLightningStrikeMixin.java | 14 + .../sanctuary/mixin/MobHeadSnowballMixin.java | 13 + .../mixin/NativeStructureStart23Mixin.java | 8 +- .../sanctuary/mixin/NaturalSnowEditMixin.java | 22 + .../mixin/SeasonalPrecipitationMixin.java | 17 + .../sanctuary/mixin/SeasonalSnowMixin.java | 20 + .../mixin/SnowAccumulationMixin.java | 29 ++ .../sanctuary/mixin/SpawnerEggAccess.java | 10 + .../sanctuary/mixin/SpawnerEggDropMixin.java | 30 ++ .../client/AbstractContainerScreenMixin.java | 1 + .../mixin/client/FourneauFireMixin.java | 20 + .../mixin/client/PanoramaRendererAccess.java | 2 + .../mixin/client/ProgressiveFogMixin.java | 3 +- .../client/SeasonalPrecipitationMixin.java | 17 + .../mixin/client/SpecialModelsAccess.java | 14 + .../koka/sanctuary/multiblock/Metatable.java | 113 +++++ .../multiblock/MultiblockCooking.java | 2 +- .../sanctuary/multiblock/MultiblockMenu.java | 2 - .../sanctuary/multiblock/Multiblocks.java | 3 +- .../sanctuary/plans/ClayWorkshopMenu.java | 96 ++++ .../sanctuary/plans/ConstructionCatalog.java | 46 ++ .../koka/sanctuary/plans/CreationLibrary.java | 26 + .../fr/koka/sanctuary/plans/GlbModel.java | 203 ++++++++ .../fr/koka/sanctuary/plans/SchematicIO.java | 21 +- .../fr/koka/sanctuary/plans/Statuary.java | 70 +++ .../koka/sanctuary/plans/StatuaryEntity.java | 58 +++ .../sanctuary/plans/StatueDiscoveries.java | 22 + .../koka/sanctuary/plans/StatueVoxelizer.java | 18 +- .../koka/sanctuary/plans/VoxelSculpture.java | 36 ++ .../sanctuary/realtime/RealtimeService.java | 1 + .../koka/sanctuary/weather/DailyWeather.java | 9 +- .../sanctuary/weather/SeasonalSnowMelt.java | 107 ++++ .../sanctuary/weather/SeasonalWeather.java | 37 ++ .../sanctuary/weather/SnowAccumulation.java | 71 +++ .../sanctuary/weather/WeatherService.java | 17 +- .../koka/sanctuary/weather/WeatherStore.java | 10 +- .../city/LostCityUndergroundPiece20.java | 16 + .../city/LostCityUndergroundStructure22.java | 4 +- .../blockstates/black_brick_slab.json | 13 + .../blockstates/black_brick_stairs.json | 209 ++++++++ .../sanctuary/blockstates/black_bricks.json | 7 + .../sanctuary/blockstates/black_clay.json | 7 + .../blockstates/blue_brick_slab.json | 13 + .../blockstates/blue_brick_stairs.json | 209 ++++++++ .../sanctuary/blockstates/blue_bricks.json | 7 + .../sanctuary/blockstates/blue_clay.json | 7 + .../blockstates/brown_brick_slab.json | 13 + .../blockstates/brown_brick_stairs.json | 209 ++++++++ .../sanctuary/blockstates/brown_bricks.json | 7 + .../sanctuary/blockstates/brown_clay.json | 7 + .../sanctuary/blockstates/clay_workshop.json | 7 + .../blockstates/cyan_brick_slab.json | 13 + .../blockstates/cyan_brick_stairs.json | 209 ++++++++ .../sanctuary/blockstates/cyan_bricks.json | 7 + .../sanctuary/blockstates/cyan_clay.json | 7 + .../blockstates/gray_brick_slab.json | 13 + .../blockstates/gray_brick_stairs.json | 209 ++++++++ .../sanctuary/blockstates/gray_bricks.json | 7 + .../sanctuary/blockstates/gray_clay.json | 7 + .../blockstates/green_brick_slab.json | 13 + .../blockstates/green_brick_stairs.json | 209 ++++++++ .../sanctuary/blockstates/green_bricks.json | 7 + .../sanctuary/blockstates/green_clay.json | 7 + .../blockstates/light_blue_brick_slab.json | 13 + .../blockstates/light_blue_brick_stairs.json | 209 ++++++++ .../blockstates/light_blue_bricks.json | 7 + .../blockstates/light_blue_clay.json | 7 + .../blockstates/light_gray_brick_slab.json | 13 + .../blockstates/light_gray_brick_stairs.json | 209 ++++++++ .../blockstates/light_gray_bricks.json | 7 + .../blockstates/light_gray_clay.json | 7 + .../blockstates/lime_brick_slab.json | 13 + .../blockstates/lime_brick_stairs.json | 209 ++++++++ .../sanctuary/blockstates/lime_bricks.json | 7 + .../sanctuary/blockstates/lime_clay.json | 7 + .../blockstates/magenta_brick_slab.json | 13 + .../blockstates/magenta_brick_stairs.json | 209 ++++++++ .../sanctuary/blockstates/magenta_bricks.json | 7 + .../sanctuary/blockstates/magenta_clay.json | 7 + .../sanctuary/blockstates/metatable.json | 31 ++ .../blockstates/orange_brick_slab.json | 13 + .../blockstates/orange_brick_stairs.json | 209 ++++++++ .../sanctuary/blockstates/orange_bricks.json | 7 + .../sanctuary/blockstates/orange_clay.json | 7 + .../blockstates/pink_brick_slab.json | 13 + .../blockstates/pink_brick_stairs.json | 209 ++++++++ .../sanctuary/blockstates/pink_bricks.json | 7 + .../sanctuary/blockstates/pink_clay.json | 7 + .../blockstates/purple_brick_slab.json | 13 + .../blockstates/purple_brick_stairs.json | 209 ++++++++ .../sanctuary/blockstates/purple_bricks.json | 7 + .../sanctuary/blockstates/purple_clay.json | 7 + .../sanctuary/blockstates/red_brick_slab.json | 13 + .../blockstates/red_brick_stairs.json | 209 ++++++++ .../sanctuary/blockstates/red_bricks.json | 7 + .../sanctuary/blockstates/red_clay.json | 7 + .../sanctuary/blockstates/statuary.json | 7 + .../blockstates/white_brick_slab.json | 13 + .../blockstates/white_brick_stairs.json | 209 ++++++++ .../sanctuary/blockstates/white_bricks.json | 7 + .../sanctuary/blockstates/white_clay.json | 7 + .../blockstates/yellow_brick_slab.json | 13 + .../blockstates/yellow_brick_stairs.json | 209 ++++++++ .../sanctuary/blockstates/yellow_bricks.json | 7 + .../sanctuary/blockstates/yellow_clay.json | 7 + .../assets/sanctuary/items/black_brick.json | 6 + .../sanctuary/items/black_brick_slab.json | 6 + .../sanctuary/items/black_brick_stairs.json | 6 + .../assets/sanctuary/items/black_bricks.json | 6 + .../assets/sanctuary/items/black_clay.json | 6 + .../sanctuary/items/black_clay_ball.json | 6 + .../assets/sanctuary/items/blue_brick.json | 6 + .../sanctuary/items/blue_brick_slab.json | 6 + .../sanctuary/items/blue_brick_stairs.json | 6 + .../assets/sanctuary/items/blue_bricks.json | 6 + .../assets/sanctuary/items/blue_clay.json | 6 + .../sanctuary/items/blue_clay_ball.json | 6 + .../assets/sanctuary/items/brown_brick.json | 6 + .../sanctuary/items/brown_brick_slab.json | 6 + .../sanctuary/items/brown_brick_stairs.json | 6 + .../assets/sanctuary/items/brown_bricks.json | 6 + .../assets/sanctuary/items/brown_clay.json | 6 + .../sanctuary/items/brown_clay_ball.json | 6 + .../assets/sanctuary/items/clay_workshop.json | 6 + .../assets/sanctuary/items/cyan_brick.json | 6 + .../sanctuary/items/cyan_brick_slab.json | 6 + .../sanctuary/items/cyan_brick_stairs.json | 6 + .../assets/sanctuary/items/cyan_bricks.json | 6 + .../assets/sanctuary/items/cyan_clay.json | 6 + .../sanctuary/items/cyan_clay_ball.json | 6 + .../assets/sanctuary/items/gray_brick.json | 6 + .../sanctuary/items/gray_brick_slab.json | 6 + .../sanctuary/items/gray_brick_stairs.json | 6 + .../assets/sanctuary/items/gray_bricks.json | 6 + .../assets/sanctuary/items/gray_clay.json | 6 + .../sanctuary/items/gray_clay_ball.json | 6 + .../assets/sanctuary/items/green_brick.json | 6 + .../sanctuary/items/green_brick_slab.json | 6 + .../sanctuary/items/green_brick_stairs.json | 6 + .../assets/sanctuary/items/green_bricks.json | 6 + .../assets/sanctuary/items/green_clay.json | 6 + .../sanctuary/items/green_clay_ball.json | 6 + .../sanctuary/items/light_blue_brick.json | 6 + .../items/light_blue_brick_slab.json | 6 + .../items/light_blue_brick_stairs.json | 6 + .../sanctuary/items/light_blue_bricks.json | 6 + .../sanctuary/items/light_blue_clay.json | 6 + .../sanctuary/items/light_blue_clay_ball.json | 6 + .../sanctuary/items/light_gray_brick.json | 6 + .../items/light_gray_brick_slab.json | 6 + .../items/light_gray_brick_stairs.json | 6 + .../sanctuary/items/light_gray_bricks.json | 6 + .../sanctuary/items/light_gray_clay.json | 6 + .../sanctuary/items/light_gray_clay_ball.json | 6 + .../assets/sanctuary/items/lime_brick.json | 6 + .../sanctuary/items/lime_brick_slab.json | 6 + .../sanctuary/items/lime_brick_stairs.json | 6 + .../assets/sanctuary/items/lime_bricks.json | 6 + .../assets/sanctuary/items/lime_clay.json | 6 + .../sanctuary/items/lime_clay_ball.json | 6 + .../assets/sanctuary/items/magenta_brick.json | 6 + .../sanctuary/items/magenta_brick_slab.json | 6 + .../sanctuary/items/magenta_brick_stairs.json | 6 + .../sanctuary/items/magenta_bricks.json | 6 + .../assets/sanctuary/items/magenta_clay.json | 6 + .../sanctuary/items/magenta_clay_ball.json | 6 + .../assets/sanctuary/items/orange_brick.json | 6 + .../sanctuary/items/orange_brick_slab.json | 6 + .../sanctuary/items/orange_brick_stairs.json | 6 + .../assets/sanctuary/items/orange_bricks.json | 6 + .../assets/sanctuary/items/orange_clay.json | 6 + .../sanctuary/items/orange_clay_ball.json | 6 + .../assets/sanctuary/items/pink_brick.json | 6 + .../sanctuary/items/pink_brick_slab.json | 6 + .../sanctuary/items/pink_brick_stairs.json | 6 + .../assets/sanctuary/items/pink_bricks.json | 6 + .../assets/sanctuary/items/pink_clay.json | 6 + .../sanctuary/items/pink_clay_ball.json | 6 + .../assets/sanctuary/items/purple_brick.json | 6 + .../sanctuary/items/purple_brick_slab.json | 6 + .../sanctuary/items/purple_brick_stairs.json | 6 + .../assets/sanctuary/items/purple_bricks.json | 6 + .../assets/sanctuary/items/purple_clay.json | 6 + .../sanctuary/items/purple_clay_ball.json | 6 + .../assets/sanctuary/items/red_brick.json | 6 + .../sanctuary/items/red_brick_slab.json | 6 + .../sanctuary/items/red_brick_stairs.json | 6 + .../assets/sanctuary/items/red_bricks.json | 6 + .../assets/sanctuary/items/red_clay.json | 6 + .../assets/sanctuary/items/red_clay_ball.json | 6 + .../assets/sanctuary/items/statuary.json | 9 + .../assets/sanctuary/items/white_brick.json | 6 + .../sanctuary/items/white_brick_slab.json | 6 + .../sanctuary/items/white_brick_stairs.json | 6 + .../assets/sanctuary/items/white_bricks.json | 6 + .../assets/sanctuary/items/white_clay.json | 6 + .../sanctuary/items/white_clay_ball.json | 6 + .../assets/sanctuary/items/yellow_brick.json | 6 + .../sanctuary/items/yellow_brick_slab.json | 6 + .../sanctuary/items/yellow_brick_stairs.json | 6 + .../assets/sanctuary/items/yellow_bricks.json | 6 + .../assets/sanctuary/items/yellow_clay.json | 6 + .../sanctuary/items/yellow_clay_ball.json | 6 + .../assets/sanctuary/lang/en_us.json | 213 +++++++- .../assets/sanctuary/lang/fr_fr.json | 213 +++++++- .../models/block/black_brick_slab.json | 8 + .../models/block/black_brick_slab_top.json | 8 + .../models/block/black_brick_stairs.json | 8 + .../block/black_brick_stairs_inner.json | 8 + .../block/black_brick_stairs_outer.json | 8 + .../sanctuary/models/block/black_bricks.json | 6 + .../sanctuary/models/block/black_clay.json | 6 + .../models/block/blue_brick_slab.json | 8 + .../models/block/blue_brick_slab_top.json | 8 + .../models/block/blue_brick_stairs.json | 8 + .../models/block/blue_brick_stairs_inner.json | 8 + .../models/block/blue_brick_stairs_outer.json | 8 + .../sanctuary/models/block/blue_bricks.json | 6 + .../sanctuary/models/block/blue_clay.json | 6 + .../models/block/brown_brick_slab.json | 8 + .../models/block/brown_brick_slab_top.json | 8 + .../models/block/brown_brick_stairs.json | 8 + .../block/brown_brick_stairs_inner.json | 8 + .../block/brown_brick_stairs_outer.json | 8 + .../sanctuary/models/block/brown_bricks.json | 6 + .../sanctuary/models/block/brown_clay.json | 6 + .../sanctuary/models/block/clay_workshop.json | 139 ++++++ .../models/block/cyan_brick_slab.json | 8 + .../models/block/cyan_brick_slab_top.json | 8 + .../models/block/cyan_brick_stairs.json | 8 + .../models/block/cyan_brick_stairs_inner.json | 8 + .../models/block/cyan_brick_stairs_outer.json | 8 + .../sanctuary/models/block/cyan_bricks.json | 6 + .../sanctuary/models/block/cyan_clay.json | 6 + .../models/block/fourneau/east_0_0_0_lit.json | 85 ++++ .../models/block/fourneau/east_0_0_1_lit.json | 85 ++++ .../models/block/fourneau/east_0_0_2_lit.json | 85 ++++ .../models/block/fourneau/east_0_1_0_lit.json | 85 ++++ .../models/block/fourneau/east_0_1_1_lit.json | 85 ++++ .../models/block/fourneau/east_0_1_2_lit.json | 85 ++++ .../models/block/fourneau/east_0_2_0_lit.json | 85 ++++ .../models/block/fourneau/east_0_2_1_lit.json | 85 ++++ .../models/block/fourneau/east_0_2_2_lit.json | 85 ++++ .../models/block/fourneau/east_1_0_0_lit.json | 85 ++++ .../models/block/fourneau/east_1_0_1_lit.json | 85 ++++ .../models/block/fourneau/east_1_0_2_lit.json | 85 ++++ .../models/block/fourneau/east_1_1_0_lit.json | 85 ++++ .../models/block/fourneau/east_1_1_1_lit.json | 85 ++++ .../models/block/fourneau/east_1_1_2_lit.json | 85 ++++ .../models/block/fourneau/east_1_2_0_lit.json | 85 ++++ .../models/block/fourneau/east_1_2_1_lit.json | 85 ++++ .../models/block/fourneau/east_1_2_2_lit.json | 85 ++++ .../models/block/fourneau/east_2_0_0_lit.json | 373 ++++++++++++++ .../models/block/fourneau/east_2_0_1_lit.json | 397 +++++++++++++++ .../models/block/fourneau/east_2_0_2_lit.json | 325 ++++++++++++ .../models/block/fourneau/east_2_1_0_lit.json | 181 +++++++ .../models/block/fourneau/east_2_1_1_lit.json | 301 +++++++++++ .../models/block/fourneau/east_2_1_2_lit.json | 181 +++++++ .../models/block/fourneau/east_2_2_0_lit.json | 181 +++++++ .../models/block/fourneau/east_2_2_1_lit.json | 325 ++++++++++++ .../models/block/fourneau/east_2_2_2_lit.json | 181 +++++++ .../block/fourneau/north_0_0_0_lit.json | 373 ++++++++++++++ .../block/fourneau/north_0_0_1_lit.json | 85 ++++ .../block/fourneau/north_0_0_2_lit.json | 85 ++++ .../block/fourneau/north_0_1_0_lit.json | 181 +++++++ .../block/fourneau/north_0_1_1_lit.json | 85 ++++ .../block/fourneau/north_0_1_2_lit.json | 85 ++++ .../block/fourneau/north_0_2_0_lit.json | 181 +++++++ .../block/fourneau/north_0_2_1_lit.json | 85 ++++ .../block/fourneau/north_0_2_2_lit.json | 85 ++++ .../block/fourneau/north_1_0_0_lit.json | 397 +++++++++++++++ .../block/fourneau/north_1_0_1_lit.json | 85 ++++ .../block/fourneau/north_1_0_2_lit.json | 85 ++++ .../block/fourneau/north_1_1_0_lit.json | 301 +++++++++++ .../block/fourneau/north_1_1_1_lit.json | 85 ++++ .../block/fourneau/north_1_1_2_lit.json | 85 ++++ .../block/fourneau/north_1_2_0_lit.json | 325 ++++++++++++ .../block/fourneau/north_1_2_1_lit.json | 85 ++++ .../block/fourneau/north_1_2_2_lit.json | 85 ++++ .../block/fourneau/north_2_0_0_lit.json | 325 ++++++++++++ .../block/fourneau/north_2_0_1_lit.json | 85 ++++ .../block/fourneau/north_2_0_2_lit.json | 85 ++++ .../block/fourneau/north_2_1_0_lit.json | 181 +++++++ .../block/fourneau/north_2_1_1_lit.json | 85 ++++ .../block/fourneau/north_2_1_2_lit.json | 85 ++++ .../block/fourneau/north_2_2_0_lit.json | 181 +++++++ .../block/fourneau/north_2_2_1_lit.json | 85 ++++ .../block/fourneau/north_2_2_2_lit.json | 85 ++++ .../block/fourneau/south_0_0_0_lit.json | 85 ++++ .../block/fourneau/south_0_0_1_lit.json | 85 ++++ .../block/fourneau/south_0_0_2_lit.json | 325 ++++++++++++ .../block/fourneau/south_0_1_0_lit.json | 85 ++++ .../block/fourneau/south_0_1_1_lit.json | 85 ++++ .../block/fourneau/south_0_1_2_lit.json | 181 +++++++ .../block/fourneau/south_0_2_0_lit.json | 85 ++++ .../block/fourneau/south_0_2_1_lit.json | 85 ++++ .../block/fourneau/south_0_2_2_lit.json | 181 +++++++ .../block/fourneau/south_1_0_0_lit.json | 85 ++++ .../block/fourneau/south_1_0_1_lit.json | 85 ++++ .../block/fourneau/south_1_0_2_lit.json | 397 +++++++++++++++ .../block/fourneau/south_1_1_0_lit.json | 85 ++++ .../block/fourneau/south_1_1_1_lit.json | 85 ++++ .../block/fourneau/south_1_1_2_lit.json | 301 +++++++++++ .../block/fourneau/south_1_2_0_lit.json | 85 ++++ .../block/fourneau/south_1_2_1_lit.json | 85 ++++ .../block/fourneau/south_1_2_2_lit.json | 325 ++++++++++++ .../block/fourneau/south_2_0_0_lit.json | 85 ++++ .../block/fourneau/south_2_0_1_lit.json | 85 ++++ .../block/fourneau/south_2_0_2_lit.json | 373 ++++++++++++++ .../block/fourneau/south_2_1_0_lit.json | 85 ++++ .../block/fourneau/south_2_1_1_lit.json | 85 ++++ .../block/fourneau/south_2_1_2_lit.json | 181 +++++++ .../block/fourneau/south_2_2_0_lit.json | 85 ++++ .../block/fourneau/south_2_2_1_lit.json | 85 ++++ .../block/fourneau/south_2_2_2_lit.json | 181 +++++++ .../models/block/fourneau/west_0_0_0_lit.json | 325 ++++++++++++ .../models/block/fourneau/west_0_0_1_lit.json | 397 +++++++++++++++ .../models/block/fourneau/west_0_0_2_lit.json | 373 ++++++++++++++ .../models/block/fourneau/west_0_1_0_lit.json | 181 +++++++ .../models/block/fourneau/west_0_1_1_lit.json | 301 +++++++++++ .../models/block/fourneau/west_0_1_2_lit.json | 181 +++++++ .../models/block/fourneau/west_0_2_0_lit.json | 181 +++++++ .../models/block/fourneau/west_0_2_1_lit.json | 325 ++++++++++++ .../models/block/fourneau/west_0_2_2_lit.json | 181 +++++++ .../models/block/fourneau/west_1_0_0_lit.json | 85 ++++ .../models/block/fourneau/west_1_0_1_lit.json | 85 ++++ .../models/block/fourneau/west_1_0_2_lit.json | 85 ++++ .../models/block/fourneau/west_1_1_0_lit.json | 85 ++++ .../models/block/fourneau/west_1_1_1_lit.json | 85 ++++ .../models/block/fourneau/west_1_1_2_lit.json | 85 ++++ .../models/block/fourneau/west_1_2_0_lit.json | 85 ++++ .../models/block/fourneau/west_1_2_1_lit.json | 85 ++++ .../models/block/fourneau/west_1_2_2_lit.json | 85 ++++ .../models/block/fourneau/west_2_0_0_lit.json | 85 ++++ .../models/block/fourneau/west_2_0_1_lit.json | 85 ++++ .../models/block/fourneau/west_2_0_2_lit.json | 85 ++++ .../models/block/fourneau/west_2_1_0_lit.json | 85 ++++ .../models/block/fourneau/west_2_1_1_lit.json | 85 ++++ .../models/block/fourneau/west_2_1_2_lit.json | 85 ++++ .../models/block/fourneau/west_2_2_0_lit.json | 85 ++++ .../models/block/fourneau/west_2_2_1_lit.json | 85 ++++ .../models/block/fourneau/west_2_2_2_lit.json | 85 ++++ .../models/block/gray_brick_slab.json | 8 + .../models/block/gray_brick_slab_top.json | 8 + .../models/block/gray_brick_stairs.json | 8 + .../models/block/gray_brick_stairs_inner.json | 8 + .../models/block/gray_brick_stairs_outer.json | 8 + .../sanctuary/models/block/gray_bricks.json | 6 + .../sanctuary/models/block/gray_clay.json | 6 + .../models/block/green_brick_slab.json | 8 + .../models/block/green_brick_slab_top.json | 8 + .../models/block/green_brick_stairs.json | 8 + .../block/green_brick_stairs_inner.json | 8 + .../block/green_brick_stairs_outer.json | 8 + .../sanctuary/models/block/green_bricks.json | 6 + .../sanctuary/models/block/green_clay.json | 6 + .../models/block/light_blue_brick_slab.json | 8 + .../block/light_blue_brick_slab_top.json | 8 + .../models/block/light_blue_brick_stairs.json | 8 + .../block/light_blue_brick_stairs_inner.json | 8 + .../block/light_blue_brick_stairs_outer.json | 8 + .../models/block/light_blue_bricks.json | 6 + .../models/block/light_blue_clay.json | 6 + .../models/block/light_gray_brick_slab.json | 8 + .../block/light_gray_brick_slab_top.json | 8 + .../models/block/light_gray_brick_stairs.json | 8 + .../block/light_gray_brick_stairs_inner.json | 8 + .../block/light_gray_brick_stairs_outer.json | 8 + .../models/block/light_gray_bricks.json | 6 + .../models/block/light_gray_clay.json | 6 + .../models/block/lime_brick_slab.json | 8 + .../models/block/lime_brick_slab_top.json | 8 + .../models/block/lime_brick_stairs.json | 8 + .../models/block/lime_brick_stairs_inner.json | 8 + .../models/block/lime_brick_stairs_outer.json | 8 + .../sanctuary/models/block/lime_bricks.json | 6 + .../sanctuary/models/block/lime_clay.json | 6 + .../models/block/magenta_brick_slab.json | 8 + .../models/block/magenta_brick_slab_top.json | 8 + .../models/block/magenta_brick_stairs.json | 8 + .../block/magenta_brick_stairs_inner.json | 8 + .../block/magenta_brick_stairs_outer.json | 8 + .../models/block/magenta_bricks.json | 6 + .../sanctuary/models/block/magenta_clay.json | 6 + .../sanctuary/models/block/metatable/0.json | 84 ++++ .../sanctuary/models/block/metatable/1.json | 84 ++++ .../sanctuary/models/block/metatable/2.json | 84 ++++ .../sanctuary/models/block/metatable/3.json | 84 ++++ .../sanctuary/models/block/metatable/4.json | 84 ++++ .../sanctuary/models/block/metatable/5.json | 84 ++++ .../sanctuary/models/block/metatable/6.json | 84 ++++ .../sanctuary/models/block/metatable/7.json | 84 ++++ .../sanctuary/models/block/metatable/8.json | 84 ++++ .../models/block/orange_brick_slab.json | 8 + .../models/block/orange_brick_slab_top.json | 8 + .../models/block/orange_brick_stairs.json | 8 + .../block/orange_brick_stairs_inner.json | 8 + .../block/orange_brick_stairs_outer.json | 8 + .../sanctuary/models/block/orange_bricks.json | 6 + .../sanctuary/models/block/orange_clay.json | 6 + .../models/block/pink_brick_slab.json | 8 + .../models/block/pink_brick_slab_top.json | 8 + .../models/block/pink_brick_stairs.json | 8 + .../models/block/pink_brick_stairs_inner.json | 8 + .../models/block/pink_brick_stairs_outer.json | 8 + .../sanctuary/models/block/pink_bricks.json | 6 + .../sanctuary/models/block/pink_clay.json | 6 + .../models/block/purple_brick_slab.json | 8 + .../models/block/purple_brick_slab_top.json | 8 + .../models/block/purple_brick_stairs.json | 8 + .../block/purple_brick_stairs_inner.json | 8 + .../block/purple_brick_stairs_outer.json | 8 + .../sanctuary/models/block/purple_bricks.json | 6 + .../sanctuary/models/block/purple_clay.json | 6 + .../models/block/red_brick_slab.json | 8 + .../models/block/red_brick_slab_top.json | 8 + .../models/block/red_brick_stairs.json | 8 + .../models/block/red_brick_stairs_inner.json | 8 + .../models/block/red_brick_stairs_outer.json | 8 + .../sanctuary/models/block/red_bricks.json | 6 + .../sanctuary/models/block/red_clay.json | 6 + .../sanctuary/models/block/statuary.json | 7 + .../block/super_piston/base_inner_4.json | 48 +- .../block/super_piston/head_false_4.json | 48 +- .../block/super_piston/head_true_4.json | 48 +- .../models/block/super_piston/rod.json | 48 +- .../models/block/white_brick_slab.json | 8 + .../models/block/white_brick_slab_top.json | 8 + .../models/block/white_brick_stairs.json | 8 + .../block/white_brick_stairs_inner.json | 8 + .../block/white_brick_stairs_outer.json | 8 + .../sanctuary/models/block/white_bricks.json | 6 + .../sanctuary/models/block/white_clay.json | 6 + .../models/block/yellow_brick_slab.json | 8 + .../models/block/yellow_brick_slab_top.json | 8 + .../models/block/yellow_brick_stairs.json | 8 + .../block/yellow_brick_stairs_inner.json | 8 + .../block/yellow_brick_stairs_outer.json | 8 + .../sanctuary/models/block/yellow_bricks.json | 6 + .../sanctuary/models/block/yellow_clay.json | 6 + .../sanctuary/models/item/black_brick.json | 6 + .../models/item/black_clay_ball.json | 6 + .../sanctuary/models/item/blue_brick.json | 6 + .../sanctuary/models/item/blue_clay_ball.json | 6 + .../sanctuary/models/item/brown_brick.json | 6 + .../models/item/brown_clay_ball.json | 6 + .../sanctuary/models/item/cyan_brick.json | 6 + .../sanctuary/models/item/cyan_clay_ball.json | 6 + .../sanctuary/models/item/golden_wrench.json | 192 +------ .../sanctuary/models/item/gray_brick.json | 6 + .../sanctuary/models/item/gray_clay_ball.json | 6 + .../sanctuary/models/item/green_brick.json | 6 + .../models/item/green_clay_ball.json | 6 + .../models/item/light_blue_brick.json | 6 + .../models/item/light_blue_clay_ball.json | 6 + .../models/item/light_gray_brick.json | 6 + .../models/item/light_gray_clay_ball.json | 6 + .../sanctuary/models/item/lime_brick.json | 6 + .../sanctuary/models/item/lime_clay_ball.json | 6 + .../sanctuary/models/item/magenta_brick.json | 6 + .../models/item/magenta_clay_ball.json | 6 + .../sanctuary/models/item/orange_brick.json | 6 + .../models/item/orange_clay_ball.json | 6 + .../sanctuary/models/item/pink_brick.json | 6 + .../sanctuary/models/item/pink_clay_ball.json | 6 + .../sanctuary/models/item/purple_brick.json | 6 + .../models/item/purple_clay_ball.json | 6 + .../sanctuary/models/item/red_brick.json | 6 + .../sanctuary/models/item/red_clay_ball.json | 6 + .../sanctuary/models/item/statuary.json | 44 ++ .../sanctuary/models/item/white_brick.json | 6 + .../models/item/white_clay_ball.json | 6 + .../sanctuary/models/item/yellow_brick.json | 6 + .../models/item/yellow_clay_ball.json | 6 + .../sanctuary/textures/block/black_bricks.png | Bin 0 -> 378 bytes .../sanctuary/textures/block/black_clay.png | Bin 0 -> 399 bytes .../sanctuary/textures/block/blue_bricks.png | Bin 0 -> 416 bytes .../sanctuary/textures/block/blue_clay.png | Bin 0 -> 410 bytes .../sanctuary/textures/block/brown_bricks.png | Bin 0 -> 403 bytes .../sanctuary/textures/block/brown_clay.png | Bin 0 -> 409 bytes .../sanctuary/textures/block/cyan_bricks.png | Bin 0 -> 407 bytes .../sanctuary/textures/block/cyan_clay.png | Bin 0 -> 410 bytes .../textures/block/fourneau/front_on.png | Bin 0 -> 5814 bytes .../sanctuary/textures/block/gray_bricks.png | Bin 0 -> 405 bytes .../sanctuary/textures/block/gray_clay.png | Bin 0 -> 398 bytes .../sanctuary/textures/block/green_bricks.png | Bin 0 -> 432 bytes .../sanctuary/textures/block/green_clay.png | Bin 0 -> 410 bytes .../textures/block/light_blue_bricks.png | Bin 0 -> 435 bytes .../textures/block/light_blue_clay.png | Bin 0 -> 410 bytes .../textures/block/light_gray_bricks.png | Bin 0 -> 429 bytes .../textures/block/light_gray_clay.png | Bin 0 -> 408 bytes .../sanctuary/textures/block/lime_bricks.png | Bin 0 -> 424 bytes .../sanctuary/textures/block/lime_clay.png | Bin 0 -> 410 bytes .../textures/block/magenta_bricks.png | Bin 0 -> 486 bytes .../sanctuary/textures/block/magenta_clay.png | Bin 0 -> 409 bytes .../textures/block/metatable/side.png | Bin 0 -> 422 bytes .../textures/block/metatable/top.png | Bin 0 -> 396 bytes .../textures/block/orange_bricks.png | Bin 0 -> 450 bytes .../sanctuary/textures/block/orange_clay.png | Bin 0 -> 410 bytes .../sanctuary/textures/block/pink_bricks.png | Bin 0 -> 375 bytes .../sanctuary/textures/block/pink_clay.png | Bin 0 -> 413 bytes .../textures/block/purple_bricks.png | Bin 0 -> 435 bytes .../sanctuary/textures/block/purple_clay.png | Bin 0 -> 410 bytes .../sanctuary/textures/block/red_bricks.png | Bin 0 -> 417 bytes .../sanctuary/textures/block/red_clay.png | Bin 0 -> 409 bytes .../sanctuary/textures/block/white_bricks.png | Bin 0 -> 469 bytes .../sanctuary/textures/block/white_clay.png | Bin 0 -> 414 bytes .../textures/block/yellow_bricks.png | Bin 0 -> 506 bytes .../sanctuary/textures/block/yellow_clay.png | Bin 0 -> 410 bytes .../sanctuary/textures/item/black_brick.png | Bin 0 -> 228 bytes .../textures/item/black_clay_ball.png | Bin 0 -> 275 bytes .../sanctuary/textures/item/blue_brick.png | Bin 0 -> 240 bytes .../textures/item/blue_clay_ball.png | Bin 0 -> 297 bytes .../sanctuary/textures/item/brown_brick.png | Bin 0 -> 232 bytes .../textures/item/brown_clay_ball.png | Bin 0 -> 298 bytes .../sanctuary/textures/item/cyan_brick.png | Bin 0 -> 233 bytes .../textures/item/cyan_clay_ball.png | Bin 0 -> 299 bytes .../sanctuary/textures/item/golden_wrench.png | Bin 0 -> 180 bytes .../sanctuary/textures/item/gray_brick.png | Bin 0 -> 235 bytes .../textures/item/gray_clay_ball.png | Bin 0 -> 295 bytes .../sanctuary/textures/item/green_brick.png | Bin 0 -> 236 bytes .../textures/item/green_clay_ball.png | Bin 0 -> 301 bytes .../textures/item/light_blue_brick.png | Bin 0 -> 235 bytes .../textures/item/light_blue_clay_ball.png | Bin 0 -> 303 bytes .../textures/item/light_gray_brick.png | Bin 0 -> 234 bytes .../textures/item/light_gray_clay_ball.png | Bin 0 -> 297 bytes .../sanctuary/textures/item/lime_brick.png | Bin 0 -> 234 bytes .../textures/item/lime_clay_ball.png | Bin 0 -> 301 bytes .../sanctuary/textures/item/magenta_brick.png | Bin 0 -> 233 bytes .../textures/item/magenta_clay_ball.png | Bin 0 -> 305 bytes .../sanctuary/textures/item/orange_brick.png | Bin 0 -> 230 bytes .../textures/item/orange_clay_ball.png | Bin 0 -> 306 bytes .../sanctuary/textures/item/pink_brick.png | Bin 0 -> 234 bytes .../textures/item/pink_clay_ball.png | Bin 0 -> 299 bytes .../sanctuary/textures/item/purple_brick.png | Bin 0 -> 243 bytes .../textures/item/purple_clay_ball.png | Bin 0 -> 302 bytes .../sanctuary/textures/item/red_brick.png | Bin 0 -> 235 bytes .../sanctuary/textures/item/red_clay_ball.png | Bin 0 -> 299 bytes .../sanctuary/textures/item/white_brick.png | Bin 0 -> 232 bytes .../textures/item/white_clay_ball.png | Bin 0 -> 311 bytes .../sanctuary/textures/item/yellow_brick.png | Bin 0 -> 234 bytes .../textures/item/yellow_clay_ball.png | Bin 0 -> 300 bytes .../textures/misc/sculpture_white.png | Bin 0 -> 68 bytes .../minecraft/tags/block/mineable/axe.json | 6 + .../tags/block/mineable/pickaxe.json | 53 ++ .../minecraft/tags/block/mineable/shovel.json | 21 + .../data/minecraft/tags/block/slabs.json | 21 + .../data/minecraft/tags/block/stairs.json | 21 + .../data/minecraft/tags/item/slabs.json | 21 + .../data/minecraft/tags/item/stairs.json | 21 + .../colored_bricks/black_brick_from_dye.json | 32 ++ .../colored_bricks/black_brick_slab.json | 32 ++ .../black_brick_slab_from_stonecutting.json | 32 ++ .../colored_bricks/black_brick_stairs.json | 32 ++ .../black_brick_stairs_from_stonecutting.json | 32 ++ .../recipes/colored_bricks/black_bricks.json | 32 ++ .../colored_bricks/black_bricks_from_dye.json | 32 ++ .../recipes/colored_bricks/black_clay.json | 32 ++ .../black_clay_ball_from_dye.json | 32 ++ .../black_clay_ball_smelting.json | 32 ++ .../colored_bricks/black_clay_from_dye.json | 32 ++ .../colored_bricks/black_clay_smelting.json | 32 ++ .../colored_bricks/blue_brick_from_dye.json | 32 ++ .../colored_bricks/blue_brick_slab.json | 32 ++ .../blue_brick_slab_from_stonecutting.json | 32 ++ .../colored_bricks/blue_brick_stairs.json | 32 ++ .../blue_brick_stairs_from_stonecutting.json | 32 ++ .../recipes/colored_bricks/blue_bricks.json | 32 ++ .../colored_bricks/blue_bricks_from_dye.json | 32 ++ .../recipes/colored_bricks/blue_clay.json | 32 ++ .../blue_clay_ball_from_dye.json | 32 ++ .../blue_clay_ball_smelting.json | 32 ++ .../colored_bricks/blue_clay_from_dye.json | 32 ++ .../colored_bricks/blue_clay_smelting.json | 32 ++ .../colored_bricks/brown_brick_from_dye.json | 32 ++ .../colored_bricks/brown_brick_slab.json | 32 ++ .../brown_brick_slab_from_stonecutting.json | 32 ++ .../colored_bricks/brown_brick_stairs.json | 32 ++ .../brown_brick_stairs_from_stonecutting.json | 32 ++ .../recipes/colored_bricks/brown_bricks.json | 32 ++ .../colored_bricks/brown_bricks_from_dye.json | 32 ++ .../recipes/colored_bricks/brown_clay.json | 32 ++ .../brown_clay_ball_from_dye.json | 32 ++ .../brown_clay_ball_smelting.json | 32 ++ .../colored_bricks/brown_clay_from_dye.json | 32 ++ .../colored_bricks/brown_clay_smelting.json | 32 ++ .../colored_bricks/cyan_brick_from_dye.json | 32 ++ .../colored_bricks/cyan_brick_slab.json | 32 ++ .../cyan_brick_slab_from_stonecutting.json | 32 ++ .../colored_bricks/cyan_brick_stairs.json | 32 ++ .../cyan_brick_stairs_from_stonecutting.json | 32 ++ .../recipes/colored_bricks/cyan_bricks.json | 32 ++ .../colored_bricks/cyan_bricks_from_dye.json | 32 ++ .../recipes/colored_bricks/cyan_clay.json | 32 ++ .../cyan_clay_ball_from_dye.json | 32 ++ .../cyan_clay_ball_smelting.json | 32 ++ .../colored_bricks/cyan_clay_from_dye.json | 32 ++ .../colored_bricks/cyan_clay_smelting.json | 32 ++ .../colored_bricks/gray_brick_from_dye.json | 32 ++ .../colored_bricks/gray_brick_slab.json | 32 ++ .../gray_brick_slab_from_stonecutting.json | 32 ++ .../colored_bricks/gray_brick_stairs.json | 32 ++ .../gray_brick_stairs_from_stonecutting.json | 32 ++ .../recipes/colored_bricks/gray_bricks.json | 32 ++ .../colored_bricks/gray_bricks_from_dye.json | 32 ++ .../recipes/colored_bricks/gray_clay.json | 32 ++ .../gray_clay_ball_from_dye.json | 32 ++ .../gray_clay_ball_smelting.json | 32 ++ .../colored_bricks/gray_clay_from_dye.json | 32 ++ .../colored_bricks/gray_clay_smelting.json | 32 ++ .../colored_bricks/green_brick_from_dye.json | 32 ++ .../colored_bricks/green_brick_slab.json | 32 ++ .../green_brick_slab_from_stonecutting.json | 32 ++ .../colored_bricks/green_brick_stairs.json | 32 ++ .../green_brick_stairs_from_stonecutting.json | 32 ++ .../recipes/colored_bricks/green_bricks.json | 32 ++ .../colored_bricks/green_bricks_from_dye.json | 32 ++ .../recipes/colored_bricks/green_clay.json | 32 ++ .../green_clay_ball_from_dye.json | 32 ++ .../green_clay_ball_smelting.json | 32 ++ .../colored_bricks/green_clay_from_dye.json | 32 ++ .../colored_bricks/green_clay_smelting.json | 32 ++ .../light_blue_brick_from_dye.json | 32 ++ .../colored_bricks/light_blue_brick_slab.json | 32 ++ ...ght_blue_brick_slab_from_stonecutting.json | 32 ++ .../light_blue_brick_stairs.json | 32 ++ ...t_blue_brick_stairs_from_stonecutting.json | 32 ++ .../colored_bricks/light_blue_bricks.json | 32 ++ .../light_blue_bricks_from_dye.json | 32 ++ .../colored_bricks/light_blue_clay.json | 32 ++ .../light_blue_clay_ball_from_dye.json | 32 ++ .../light_blue_clay_ball_smelting.json | 32 ++ .../light_blue_clay_from_dye.json | 32 ++ .../light_blue_clay_smelting.json | 32 ++ .../light_gray_brick_from_dye.json | 32 ++ .../colored_bricks/light_gray_brick_slab.json | 32 ++ ...ght_gray_brick_slab_from_stonecutting.json | 32 ++ .../light_gray_brick_stairs.json | 32 ++ ...t_gray_brick_stairs_from_stonecutting.json | 32 ++ .../colored_bricks/light_gray_bricks.json | 32 ++ .../light_gray_bricks_from_dye.json | 32 ++ .../colored_bricks/light_gray_clay.json | 32 ++ .../light_gray_clay_ball_from_dye.json | 32 ++ .../light_gray_clay_ball_smelting.json | 32 ++ .../light_gray_clay_from_dye.json | 32 ++ .../light_gray_clay_smelting.json | 32 ++ .../colored_bricks/lime_brick_from_dye.json | 32 ++ .../colored_bricks/lime_brick_slab.json | 32 ++ .../lime_brick_slab_from_stonecutting.json | 32 ++ .../colored_bricks/lime_brick_stairs.json | 32 ++ .../lime_brick_stairs_from_stonecutting.json | 32 ++ .../recipes/colored_bricks/lime_bricks.json | 32 ++ .../colored_bricks/lime_bricks_from_dye.json | 32 ++ .../recipes/colored_bricks/lime_clay.json | 32 ++ .../lime_clay_ball_from_dye.json | 32 ++ .../lime_clay_ball_smelting.json | 32 ++ .../colored_bricks/lime_clay_from_dye.json | 32 ++ .../colored_bricks/lime_clay_smelting.json | 32 ++ .../magenta_brick_from_dye.json | 32 ++ .../colored_bricks/magenta_brick_slab.json | 32 ++ .../magenta_brick_slab_from_stonecutting.json | 32 ++ .../colored_bricks/magenta_brick_stairs.json | 32 ++ ...agenta_brick_stairs_from_stonecutting.json | 32 ++ .../colored_bricks/magenta_bricks.json | 32 ++ .../magenta_bricks_from_dye.json | 32 ++ .../recipes/colored_bricks/magenta_clay.json | 32 ++ .../magenta_clay_ball_from_dye.json | 32 ++ .../magenta_clay_ball_smelting.json | 32 ++ .../colored_bricks/magenta_clay_from_dye.json | 32 ++ .../colored_bricks/magenta_clay_smelting.json | 32 ++ .../colored_bricks/orange_brick_from_dye.json | 32 ++ .../colored_bricks/orange_brick_slab.json | 32 ++ .../orange_brick_slab_from_stonecutting.json | 32 ++ .../colored_bricks/orange_brick_stairs.json | 32 ++ ...orange_brick_stairs_from_stonecutting.json | 32 ++ .../recipes/colored_bricks/orange_bricks.json | 32 ++ .../orange_bricks_from_dye.json | 32 ++ .../recipes/colored_bricks/orange_clay.json | 32 ++ .../orange_clay_ball_from_dye.json | 32 ++ .../orange_clay_ball_smelting.json | 32 ++ .../colored_bricks/orange_clay_from_dye.json | 32 ++ .../colored_bricks/orange_clay_smelting.json | 32 ++ .../colored_bricks/pink_brick_from_dye.json | 32 ++ .../colored_bricks/pink_brick_slab.json | 32 ++ .../pink_brick_slab_from_stonecutting.json | 32 ++ .../colored_bricks/pink_brick_stairs.json | 32 ++ .../pink_brick_stairs_from_stonecutting.json | 32 ++ .../recipes/colored_bricks/pink_bricks.json | 32 ++ .../colored_bricks/pink_bricks_from_dye.json | 32 ++ .../recipes/colored_bricks/pink_clay.json | 32 ++ .../pink_clay_ball_from_dye.json | 32 ++ .../pink_clay_ball_smelting.json | 32 ++ .../colored_bricks/pink_clay_from_dye.json | 32 ++ .../colored_bricks/pink_clay_smelting.json | 32 ++ .../colored_bricks/purple_brick_from_dye.json | 32 ++ .../colored_bricks/purple_brick_slab.json | 32 ++ .../purple_brick_slab_from_stonecutting.json | 32 ++ .../colored_bricks/purple_brick_stairs.json | 32 ++ ...purple_brick_stairs_from_stonecutting.json | 32 ++ .../recipes/colored_bricks/purple_bricks.json | 32 ++ .../purple_bricks_from_dye.json | 32 ++ .../recipes/colored_bricks/purple_clay.json | 32 ++ .../purple_clay_ball_from_dye.json | 32 ++ .../purple_clay_ball_smelting.json | 32 ++ .../colored_bricks/purple_clay_from_dye.json | 32 ++ .../colored_bricks/purple_clay_smelting.json | 32 ++ .../colored_bricks/red_brick_from_dye.json | 32 ++ .../colored_bricks/red_brick_slab.json | 32 ++ .../red_brick_slab_from_stonecutting.json | 32 ++ .../colored_bricks/red_brick_stairs.json | 32 ++ .../red_brick_stairs_from_stonecutting.json | 32 ++ .../recipes/colored_bricks/red_bricks.json | 32 ++ .../colored_bricks/red_bricks_from_dye.json | 32 ++ .../recipes/colored_bricks/red_clay.json | 32 ++ .../red_clay_ball_from_dye.json | 32 ++ .../red_clay_ball_smelting.json | 32 ++ .../colored_bricks/red_clay_from_dye.json | 32 ++ .../colored_bricks/red_clay_smelting.json | 32 ++ .../colored_bricks/white_brick_from_dye.json | 32 ++ .../colored_bricks/white_brick_slab.json | 32 ++ .../white_brick_slab_from_stonecutting.json | 32 ++ .../colored_bricks/white_brick_stairs.json | 32 ++ .../white_brick_stairs_from_stonecutting.json | 32 ++ .../recipes/colored_bricks/white_bricks.json | 32 ++ .../colored_bricks/white_bricks_from_dye.json | 32 ++ .../recipes/colored_bricks/white_clay.json | 32 ++ .../white_clay_ball_from_dye.json | 32 ++ .../white_clay_ball_smelting.json | 32 ++ .../colored_bricks/white_clay_from_dye.json | 32 ++ .../colored_bricks/white_clay_smelting.json | 32 ++ .../colored_bricks/yellow_brick_from_dye.json | 32 ++ .../colored_bricks/yellow_brick_slab.json | 32 ++ .../yellow_brick_slab_from_stonecutting.json | 32 ++ .../colored_bricks/yellow_brick_stairs.json | 32 ++ ...yellow_brick_stairs_from_stonecutting.json | 32 ++ .../recipes/colored_bricks/yellow_bricks.json | 32 ++ .../yellow_bricks_from_dye.json | 32 ++ .../recipes/colored_bricks/yellow_clay.json | 32 ++ .../yellow_clay_ball_from_dye.json | 32 ++ .../yellow_clay_ball_smelting.json | 32 ++ .../colored_bricks/yellow_clay_from_dye.json | 32 ++ .../colored_bricks/yellow_clay_smelting.json | 32 ++ .../recipes/decorations/clay_workshop.json | 34 ++ .../loot_table/blocks/black_brick_slab.json | 31 ++ .../loot_table/blocks/black_brick_stairs.json | 18 + .../loot_table/blocks/black_bricks.json | 18 + .../loot_table/blocks/black_clay.json | 34 ++ .../loot_table/blocks/blue_brick_slab.json | 31 ++ .../loot_table/blocks/blue_brick_stairs.json | 18 + .../loot_table/blocks/blue_bricks.json | 18 + .../loot_table/blocks/blue_clay.json | 34 ++ .../loot_table/blocks/brown_brick_slab.json | 31 ++ .../loot_table/blocks/brown_brick_stairs.json | 18 + .../loot_table/blocks/brown_bricks.json | 18 + .../loot_table/blocks/brown_clay.json | 34 ++ .../loot_table/blocks/cyan_brick_slab.json | 31 ++ .../loot_table/blocks/cyan_brick_stairs.json | 18 + .../loot_table/blocks/cyan_bricks.json | 18 + .../loot_table/blocks/cyan_clay.json | 34 ++ .../loot_table/blocks/gray_brick_slab.json | 31 ++ .../loot_table/blocks/gray_brick_stairs.json | 18 + .../loot_table/blocks/gray_bricks.json | 18 + .../loot_table/blocks/gray_clay.json | 34 ++ .../loot_table/blocks/green_brick_slab.json | 31 ++ .../loot_table/blocks/green_brick_stairs.json | 18 + .../loot_table/blocks/green_bricks.json | 18 + .../loot_table/blocks/green_clay.json | 34 ++ .../blocks/light_blue_brick_slab.json | 31 ++ .../blocks/light_blue_brick_stairs.json | 18 + .../loot_table/blocks/light_blue_bricks.json | 18 + .../loot_table/blocks/light_blue_clay.json | 34 ++ .../blocks/light_gray_brick_slab.json | 31 ++ .../blocks/light_gray_brick_stairs.json | 18 + .../loot_table/blocks/light_gray_bricks.json | 18 + .../loot_table/blocks/light_gray_clay.json | 34 ++ .../loot_table/blocks/lime_brick_slab.json | 31 ++ .../loot_table/blocks/lime_brick_stairs.json | 18 + .../loot_table/blocks/lime_bricks.json | 18 + .../loot_table/blocks/lime_clay.json | 34 ++ .../loot_table/blocks/magenta_brick_slab.json | 31 ++ .../blocks/magenta_brick_stairs.json | 18 + .../loot_table/blocks/magenta_bricks.json | 18 + .../loot_table/blocks/magenta_clay.json | 34 ++ .../loot_table/blocks/orange_brick_slab.json | 31 ++ .../blocks/orange_brick_stairs.json | 18 + .../loot_table/blocks/orange_bricks.json | 18 + .../loot_table/blocks/orange_clay.json | 34 ++ .../loot_table/blocks/pink_brick_slab.json | 31 ++ .../loot_table/blocks/pink_brick_stairs.json | 18 + .../loot_table/blocks/pink_bricks.json | 18 + .../loot_table/blocks/pink_clay.json | 34 ++ .../loot_table/blocks/purple_brick_slab.json | 31 ++ .../blocks/purple_brick_stairs.json | 18 + .../loot_table/blocks/purple_bricks.json | 18 + .../loot_table/blocks/purple_clay.json | 34 ++ .../loot_table/blocks/red_brick_slab.json | 31 ++ .../loot_table/blocks/red_brick_stairs.json | 18 + .../loot_table/blocks/red_bricks.json | 18 + .../sanctuary/loot_table/blocks/red_clay.json | 34 ++ .../loot_table/blocks/white_brick_slab.json | 31 ++ .../loot_table/blocks/white_brick_stairs.json | 18 + .../loot_table/blocks/white_bricks.json | 18 + .../loot_table/blocks/white_clay.json | 34 ++ .../loot_table/blocks/yellow_brick_slab.json | 31 ++ .../blocks/yellow_brick_stairs.json | 18 + .../loot_table/blocks/yellow_bricks.json | 18 + .../loot_table/blocks/yellow_clay.json | 34 ++ .../recipe/black_brick_from_dye.json | 17 + .../sanctuary/recipe/black_brick_slab.json | 14 + .../black_brick_slab_from_stonecutting.json | 8 + .../sanctuary/recipe/black_brick_stairs.json | 16 + .../black_brick_stairs_from_stonecutting.json | 8 + .../data/sanctuary/recipe/black_bricks.json | 15 + .../recipe/black_bricks_from_dye.json | 17 + .../data/sanctuary/recipe/black_clay.json | 15 + .../recipe/black_clay_ball_from_dye.json | 17 + .../recipe/black_clay_ball_smelting.json | 9 + .../sanctuary/recipe/black_clay_from_dye.json | 17 + .../sanctuary/recipe/black_clay_smelting.json | 9 + .../sanctuary/recipe/blue_brick_from_dye.json | 17 + .../sanctuary/recipe/blue_brick_slab.json | 14 + .../blue_brick_slab_from_stonecutting.json | 8 + .../sanctuary/recipe/blue_brick_stairs.json | 16 + .../blue_brick_stairs_from_stonecutting.json | 8 + .../data/sanctuary/recipe/blue_bricks.json | 15 + .../recipe/blue_bricks_from_dye.json | 17 + .../data/sanctuary/recipe/blue_clay.json | 15 + .../recipe/blue_clay_ball_from_dye.json | 17 + .../recipe/blue_clay_ball_smelting.json | 9 + .../sanctuary/recipe/blue_clay_from_dye.json | 17 + .../sanctuary/recipe/blue_clay_smelting.json | 9 + .../recipe/brown_brick_from_dye.json | 17 + .../sanctuary/recipe/brown_brick_slab.json | 14 + .../brown_brick_slab_from_stonecutting.json | 8 + .../sanctuary/recipe/brown_brick_stairs.json | 16 + .../brown_brick_stairs_from_stonecutting.json | 8 + .../data/sanctuary/recipe/brown_bricks.json | 15 + .../recipe/brown_bricks_from_dye.json | 17 + .../data/sanctuary/recipe/brown_clay.json | 15 + .../recipe/brown_clay_ball_from_dye.json | 17 + .../recipe/brown_clay_ball_smelting.json | 9 + .../sanctuary/recipe/brown_clay_from_dye.json | 17 + .../sanctuary/recipe/brown_clay_smelting.json | 9 + .../data/sanctuary/recipe/clay_workshop.json | 18 + .../sanctuary/recipe/cyan_brick_from_dye.json | 17 + .../sanctuary/recipe/cyan_brick_slab.json | 14 + .../cyan_brick_slab_from_stonecutting.json | 8 + .../sanctuary/recipe/cyan_brick_stairs.json | 16 + .../cyan_brick_stairs_from_stonecutting.json | 8 + .../data/sanctuary/recipe/cyan_bricks.json | 15 + .../recipe/cyan_bricks_from_dye.json | 17 + .../data/sanctuary/recipe/cyan_clay.json | 15 + .../recipe/cyan_clay_ball_from_dye.json | 17 + .../recipe/cyan_clay_ball_smelting.json | 9 + .../sanctuary/recipe/cyan_clay_from_dye.json | 17 + .../sanctuary/recipe/cyan_clay_smelting.json | 9 + .../sanctuary/recipe/gray_brick_from_dye.json | 17 + .../sanctuary/recipe/gray_brick_slab.json | 14 + .../gray_brick_slab_from_stonecutting.json | 8 + .../sanctuary/recipe/gray_brick_stairs.json | 16 + .../gray_brick_stairs_from_stonecutting.json | 8 + .../data/sanctuary/recipe/gray_bricks.json | 15 + .../recipe/gray_bricks_from_dye.json | 17 + .../data/sanctuary/recipe/gray_clay.json | 15 + .../recipe/gray_clay_ball_from_dye.json | 17 + .../recipe/gray_clay_ball_smelting.json | 9 + .../sanctuary/recipe/gray_clay_from_dye.json | 17 + .../sanctuary/recipe/gray_clay_smelting.json | 9 + .../recipe/green_brick_from_dye.json | 17 + .../sanctuary/recipe/green_brick_slab.json | 14 + .../green_brick_slab_from_stonecutting.json | 8 + .../sanctuary/recipe/green_brick_stairs.json | 16 + .../green_brick_stairs_from_stonecutting.json | 8 + .../data/sanctuary/recipe/green_bricks.json | 15 + .../recipe/green_bricks_from_dye.json | 17 + .../data/sanctuary/recipe/green_clay.json | 15 + .../recipe/green_clay_ball_from_dye.json | 17 + .../recipe/green_clay_ball_smelting.json | 9 + .../sanctuary/recipe/green_clay_from_dye.json | 17 + .../sanctuary/recipe/green_clay_smelting.json | 9 + .../recipe/light_blue_brick_from_dye.json | 17 + .../recipe/light_blue_brick_slab.json | 14 + ...ght_blue_brick_slab_from_stonecutting.json | 8 + .../recipe/light_blue_brick_stairs.json | 16 + ...t_blue_brick_stairs_from_stonecutting.json | 8 + .../sanctuary/recipe/light_blue_bricks.json | 15 + .../recipe/light_blue_bricks_from_dye.json | 17 + .../sanctuary/recipe/light_blue_clay.json | 15 + .../recipe/light_blue_clay_ball_from_dye.json | 17 + .../recipe/light_blue_clay_ball_smelting.json | 9 + .../recipe/light_blue_clay_from_dye.json | 17 + .../recipe/light_blue_clay_smelting.json | 9 + .../recipe/light_gray_brick_from_dye.json | 17 + .../recipe/light_gray_brick_slab.json | 14 + ...ght_gray_brick_slab_from_stonecutting.json | 8 + .../recipe/light_gray_brick_stairs.json | 16 + ...t_gray_brick_stairs_from_stonecutting.json | 8 + .../sanctuary/recipe/light_gray_bricks.json | 15 + .../recipe/light_gray_bricks_from_dye.json | 17 + .../sanctuary/recipe/light_gray_clay.json | 15 + .../recipe/light_gray_clay_ball_from_dye.json | 17 + .../recipe/light_gray_clay_ball_smelting.json | 9 + .../recipe/light_gray_clay_from_dye.json | 17 + .../recipe/light_gray_clay_smelting.json | 9 + .../sanctuary/recipe/lime_brick_from_dye.json | 17 + .../sanctuary/recipe/lime_brick_slab.json | 14 + .../lime_brick_slab_from_stonecutting.json | 8 + .../sanctuary/recipe/lime_brick_stairs.json | 16 + .../lime_brick_stairs_from_stonecutting.json | 8 + .../data/sanctuary/recipe/lime_bricks.json | 15 + .../recipe/lime_bricks_from_dye.json | 17 + .../data/sanctuary/recipe/lime_clay.json | 15 + .../recipe/lime_clay_ball_from_dye.json | 17 + .../recipe/lime_clay_ball_smelting.json | 9 + .../sanctuary/recipe/lime_clay_from_dye.json | 17 + .../sanctuary/recipe/lime_clay_smelting.json | 9 + .../recipe/magenta_brick_from_dye.json | 17 + .../sanctuary/recipe/magenta_brick_slab.json | 14 + .../magenta_brick_slab_from_stonecutting.json | 8 + .../recipe/magenta_brick_stairs.json | 16 + ...agenta_brick_stairs_from_stonecutting.json | 8 + .../data/sanctuary/recipe/magenta_bricks.json | 15 + .../recipe/magenta_bricks_from_dye.json | 17 + .../data/sanctuary/recipe/magenta_clay.json | 15 + .../recipe/magenta_clay_ball_from_dye.json | 17 + .../recipe/magenta_clay_ball_smelting.json | 9 + .../recipe/magenta_clay_from_dye.json | 17 + .../recipe/magenta_clay_smelting.json | 9 + .../recipe/orange_brick_from_dye.json | 17 + .../sanctuary/recipe/orange_brick_slab.json | 14 + .../orange_brick_slab_from_stonecutting.json | 8 + .../sanctuary/recipe/orange_brick_stairs.json | 16 + ...orange_brick_stairs_from_stonecutting.json | 8 + .../data/sanctuary/recipe/orange_bricks.json | 15 + .../recipe/orange_bricks_from_dye.json | 17 + .../data/sanctuary/recipe/orange_clay.json | 15 + .../recipe/orange_clay_ball_from_dye.json | 17 + .../recipe/orange_clay_ball_smelting.json | 9 + .../recipe/orange_clay_from_dye.json | 17 + .../recipe/orange_clay_smelting.json | 9 + .../sanctuary/recipe/pink_brick_from_dye.json | 17 + .../sanctuary/recipe/pink_brick_slab.json | 14 + .../pink_brick_slab_from_stonecutting.json | 8 + .../sanctuary/recipe/pink_brick_stairs.json | 16 + .../pink_brick_stairs_from_stonecutting.json | 8 + .../data/sanctuary/recipe/pink_bricks.json | 15 + .../recipe/pink_bricks_from_dye.json | 17 + .../data/sanctuary/recipe/pink_clay.json | 15 + .../recipe/pink_clay_ball_from_dye.json | 17 + .../recipe/pink_clay_ball_smelting.json | 9 + .../sanctuary/recipe/pink_clay_from_dye.json | 17 + .../sanctuary/recipe/pink_clay_smelting.json | 9 + .../recipe/purple_brick_from_dye.json | 17 + .../sanctuary/recipe/purple_brick_slab.json | 14 + .../purple_brick_slab_from_stonecutting.json | 8 + .../sanctuary/recipe/purple_brick_stairs.json | 16 + ...purple_brick_stairs_from_stonecutting.json | 8 + .../data/sanctuary/recipe/purple_bricks.json | 15 + .../recipe/purple_bricks_from_dye.json | 17 + .../data/sanctuary/recipe/purple_clay.json | 15 + .../recipe/purple_clay_ball_from_dye.json | 17 + .../recipe/purple_clay_ball_smelting.json | 9 + .../recipe/purple_clay_from_dye.json | 17 + .../recipe/purple_clay_smelting.json | 9 + .../sanctuary/recipe/red_brick_from_dye.json | 17 + .../data/sanctuary/recipe/red_brick_slab.json | 14 + .../red_brick_slab_from_stonecutting.json | 8 + .../sanctuary/recipe/red_brick_stairs.json | 16 + .../red_brick_stairs_from_stonecutting.json | 8 + .../data/sanctuary/recipe/red_bricks.json | 15 + .../sanctuary/recipe/red_bricks_from_dye.json | 17 + .../data/sanctuary/recipe/red_clay.json | 15 + .../recipe/red_clay_ball_from_dye.json | 17 + .../recipe/red_clay_ball_smelting.json | 9 + .../sanctuary/recipe/red_clay_from_dye.json | 17 + .../sanctuary/recipe/red_clay_smelting.json | 9 + .../recipe/white_brick_from_dye.json | 17 + .../sanctuary/recipe/white_brick_slab.json | 14 + .../white_brick_slab_from_stonecutting.json | 8 + .../sanctuary/recipe/white_brick_stairs.json | 16 + .../white_brick_stairs_from_stonecutting.json | 8 + .../data/sanctuary/recipe/white_bricks.json | 15 + .../recipe/white_bricks_from_dye.json | 17 + .../data/sanctuary/recipe/white_clay.json | 15 + .../recipe/white_clay_ball_from_dye.json | 17 + .../recipe/white_clay_ball_smelting.json | 9 + .../sanctuary/recipe/white_clay_from_dye.json | 17 + .../sanctuary/recipe/white_clay_smelting.json | 9 + .../recipe/yellow_brick_from_dye.json | 17 + .../sanctuary/recipe/yellow_brick_slab.json | 14 + .../yellow_brick_slab_from_stonecutting.json | 8 + .../sanctuary/recipe/yellow_brick_stairs.json | 16 + ...yellow_brick_stairs_from_stonecutting.json | 8 + .../data/sanctuary/recipe/yellow_bricks.json | 15 + .../recipe/yellow_bricks_from_dye.json | 17 + .../data/sanctuary/recipe/yellow_clay.json | 15 + .../recipe/yellow_clay_ball_from_dye.json | 17 + .../recipe/yellow_clay_ball_smelting.json | 9 + .../recipe/yellow_clay_from_dye.json | 17 + .../recipe/yellow_clay_smelting.json | 9 + .../clay_and_bricks.json | 465 ++++++++++++++++- .../sanctuary/tags/item/brick_blocks.json | 22 + .../data/sanctuary/tags/item/brick_items.json | 22 + .../data/sanctuary/tags/item/clay_balls.json | 22 + .../data/sanctuary/tags/item/clay_blocks.json | 22 + .../src/main/resources/sanctuary.mixins.json | 24 +- .../sanctuary/weather/Seasonal105Smoke.java | 22 + packwiz/pack.toml | 2 +- scripts/compile_collections.py | 4 + scripts/data/collections-sanctuary.json | 467 ++++++++++++++++++ tools/colored-bricks-palette.json | 212 ++++++++ tools/fourneau-embers.json | 1 + tools/generate-colored-bricks.py | 103 ++++ tools/generate-fourneau-models.py | 23 + tools/generate-metatable-models.py | 17 + tools/generate-super-piston-models.py | 14 +- 1111 files changed, 41665 insertions(+), 534 deletions(-) create mode 100644 docs/catalogue-progression-beta099.md create mode 100644 docs/clay-contrast-beta104.md create mode 100644 docs/clay-workshop-integration-beta110.md create mode 100644 docs/colored-bricks-beta102.md create mode 100644 docs/colored-bricks-order-beta103.md create mode 100644 docs/creative-construction-beta098.md create mode 100644 docs/exploration-familiers-beta100.md create mode 100644 docs/flying-landing-beta101.md create mode 100644 docs/fourneau-allume-beta094.md create mode 100644 docs/living-hats-seasons-beta105.md create mode 100644 docs/metabli-beta096.md create mode 100644 docs/metabli-construction.md create mode 100644 docs/piston-shaft-beta095.md create mode 100644 docs/seasonal-snow-melt-beta109.md create mode 100644 docs/snow-accumulation-beta108.md create mode 100644 docs/spawn-eggs-acquisition-beta107.md create mode 100644 docs/spawn-eggs-catalogue-beta107.md create mode 100644 docs/statuary-beta106.md create mode 100644 docs/temple-crash-beta097.md create mode 100644 docs/tooltip-crash-beta093.md create mode 100644 mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/Bricks102ClientChecks.java create mode 100644 mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/Catalogue099ClientChecks.java create mode 100644 mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/Eggs107ClientChecks.java create mode 100644 mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/Exploration100ClientChecks.java create mode 100644 mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/Landing101ClientChecks.java create mode 100644 mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/Living105ClientChecks.java create mode 100644 mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/Melt109ClientChecks.java create mode 100644 mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/Metatable096ClientChecks.java create mode 100644 mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/Schematics100ClientChecks.java create mode 100644 mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/Snow108ClientChecks.java create mode 100644 mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/Statuary106ClientChecks.java create mode 100644 mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/Temple097ClientChecks.java create mode 100644 mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/Tooltip093ClientChecks.java create mode 100644 mods/sanctuary/src/gametest/java/fr/koka/sanctuary/gametest/Fourneau094Probe.java create mode 100644 mods/sanctuary/src/gametest/java/fr/koka/sanctuary/gametest/Tooltip093Probe.java create mode 100644 mods/sanctuary/src/gametest/java/fr/koka/sanctuary/gametest/mixin/Fourneau094ParticleProbeMixin.java create mode 100644 mods/sanctuary/src/gametest/java/fr/koka/sanctuary/gametest/mixin/Tooltip093FontProbeMixin.java create mode 100644 mods/sanctuary/src/main/java/fr/koka/sanctuary/building/ColoredBricks.java create mode 100644 mods/sanctuary/src/main/java/fr/koka/sanctuary/client/ClayWorkshopScreen.java create mode 100644 mods/sanctuary/src/main/java/fr/koka/sanctuary/client/FourneauFire.java create mode 100644 mods/sanctuary/src/main/java/fr/koka/sanctuary/client/InlineAdvancements.java create mode 100644 mods/sanctuary/src/main/java/fr/koka/sanctuary/client/MetatableClient.java create mode 100644 mods/sanctuary/src/main/java/fr/koka/sanctuary/client/ModelImportErrors.java create mode 100644 mods/sanctuary/src/main/java/fr/koka/sanctuary/client/PlanHistory.java create mode 100644 mods/sanctuary/src/main/java/fr/koka/sanctuary/client/PlanMaterials.java create mode 100644 mods/sanctuary/src/main/java/fr/koka/sanctuary/client/PlanPreviewRenderer.java create mode 100644 mods/sanctuary/src/main/java/fr/koka/sanctuary/client/PlanThumbnail.java create mode 100644 mods/sanctuary/src/main/java/fr/koka/sanctuary/client/StatuaryRenderer.java create mode 100644 mods/sanctuary/src/main/java/fr/koka/sanctuary/cosmetics/MobHeadActions.java create mode 100644 mods/sanctuary/src/main/java/fr/koka/sanctuary/cosmetics/MobJournal.java create mode 100644 mods/sanctuary/src/main/java/fr/koka/sanctuary/eggs/BirthEggs.java create mode 100644 mods/sanctuary/src/main/java/fr/koka/sanctuary/familiar/FamiliarRoam.java create mode 100644 mods/sanctuary/src/main/java/fr/koka/sanctuary/mixin/BirthEggAllayMixin.java create mode 100644 mods/sanctuary/src/main/java/fr/koka/sanctuary/mixin/BirthEggAnimalMixin.java create mode 100644 mods/sanctuary/src/main/java/fr/koka/sanctuary/mixin/BirthEggSpecialAnimalMixin.java create mode 100644 mods/sanctuary/src/main/java/fr/koka/sanctuary/mixin/BirthEggTurtleMixin.java create mode 100644 mods/sanctuary/src/main/java/fr/koka/sanctuary/mixin/BirthEggUseMixin.java create mode 100644 mods/sanctuary/src/main/java/fr/koka/sanctuary/mixin/BirthEggVillagerMixin.java create mode 100644 mods/sanctuary/src/main/java/fr/koka/sanctuary/mixin/HeadSpawnerAccess.java create mode 100644 mods/sanctuary/src/main/java/fr/koka/sanctuary/mixin/MobHeadGoalAccess.java create mode 100644 mods/sanctuary/src/main/java/fr/koka/sanctuary/mixin/MobHeadLightningMixin.java create mode 100644 mods/sanctuary/src/main/java/fr/koka/sanctuary/mixin/MobHeadLightningStrikeMixin.java create mode 100644 mods/sanctuary/src/main/java/fr/koka/sanctuary/mixin/MobHeadSnowballMixin.java create mode 100644 mods/sanctuary/src/main/java/fr/koka/sanctuary/mixin/NaturalSnowEditMixin.java create mode 100644 mods/sanctuary/src/main/java/fr/koka/sanctuary/mixin/SeasonalPrecipitationMixin.java create mode 100644 mods/sanctuary/src/main/java/fr/koka/sanctuary/mixin/SeasonalSnowMixin.java create mode 100644 mods/sanctuary/src/main/java/fr/koka/sanctuary/mixin/SnowAccumulationMixin.java create mode 100644 mods/sanctuary/src/main/java/fr/koka/sanctuary/mixin/SpawnerEggAccess.java create mode 100644 mods/sanctuary/src/main/java/fr/koka/sanctuary/mixin/SpawnerEggDropMixin.java create mode 100644 mods/sanctuary/src/main/java/fr/koka/sanctuary/mixin/client/FourneauFireMixin.java create mode 100644 mods/sanctuary/src/main/java/fr/koka/sanctuary/mixin/client/SeasonalPrecipitationMixin.java create mode 100644 mods/sanctuary/src/main/java/fr/koka/sanctuary/mixin/client/SpecialModelsAccess.java create mode 100644 mods/sanctuary/src/main/java/fr/koka/sanctuary/multiblock/Metatable.java create mode 100644 mods/sanctuary/src/main/java/fr/koka/sanctuary/plans/ClayWorkshopMenu.java create mode 100644 mods/sanctuary/src/main/java/fr/koka/sanctuary/plans/ConstructionCatalog.java create mode 100644 mods/sanctuary/src/main/java/fr/koka/sanctuary/plans/CreationLibrary.java create mode 100644 mods/sanctuary/src/main/java/fr/koka/sanctuary/plans/GlbModel.java create mode 100644 mods/sanctuary/src/main/java/fr/koka/sanctuary/plans/Statuary.java create mode 100644 mods/sanctuary/src/main/java/fr/koka/sanctuary/plans/StatuaryEntity.java create mode 100644 mods/sanctuary/src/main/java/fr/koka/sanctuary/plans/StatueDiscoveries.java create mode 100644 mods/sanctuary/src/main/java/fr/koka/sanctuary/plans/VoxelSculpture.java create mode 100644 mods/sanctuary/src/main/java/fr/koka/sanctuary/weather/SeasonalSnowMelt.java create mode 100644 mods/sanctuary/src/main/java/fr/koka/sanctuary/weather/SeasonalWeather.java create mode 100644 mods/sanctuary/src/main/java/fr/koka/sanctuary/weather/SnowAccumulation.java create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/blockstates/black_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/blockstates/black_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/blockstates/black_bricks.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/blockstates/black_clay.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/blockstates/blue_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/blockstates/blue_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/blockstates/blue_bricks.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/blockstates/blue_clay.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/blockstates/brown_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/blockstates/brown_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/blockstates/brown_bricks.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/blockstates/brown_clay.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/blockstates/clay_workshop.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/blockstates/cyan_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/blockstates/cyan_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/blockstates/cyan_bricks.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/blockstates/cyan_clay.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/blockstates/gray_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/blockstates/gray_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/blockstates/gray_bricks.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/blockstates/gray_clay.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/blockstates/green_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/blockstates/green_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/blockstates/green_bricks.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/blockstates/green_clay.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/blockstates/light_blue_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/blockstates/light_blue_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/blockstates/light_blue_bricks.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/blockstates/light_blue_clay.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/blockstates/light_gray_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/blockstates/light_gray_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/blockstates/light_gray_bricks.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/blockstates/light_gray_clay.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/blockstates/lime_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/blockstates/lime_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/blockstates/lime_bricks.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/blockstates/lime_clay.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/blockstates/magenta_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/blockstates/magenta_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/blockstates/magenta_bricks.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/blockstates/magenta_clay.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/blockstates/metatable.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/blockstates/orange_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/blockstates/orange_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/blockstates/orange_bricks.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/blockstates/orange_clay.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/blockstates/pink_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/blockstates/pink_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/blockstates/pink_bricks.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/blockstates/pink_clay.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/blockstates/purple_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/blockstates/purple_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/blockstates/purple_bricks.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/blockstates/purple_clay.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/blockstates/red_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/blockstates/red_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/blockstates/red_bricks.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/blockstates/red_clay.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/blockstates/statuary.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/blockstates/white_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/blockstates/white_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/blockstates/white_bricks.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/blockstates/white_clay.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/blockstates/yellow_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/blockstates/yellow_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/blockstates/yellow_bricks.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/blockstates/yellow_clay.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/black_brick.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/black_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/black_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/black_bricks.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/black_clay.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/black_clay_ball.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/blue_brick.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/blue_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/blue_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/blue_bricks.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/blue_clay.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/blue_clay_ball.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/brown_brick.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/brown_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/brown_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/brown_bricks.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/brown_clay.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/brown_clay_ball.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/clay_workshop.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/cyan_brick.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/cyan_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/cyan_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/cyan_bricks.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/cyan_clay.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/cyan_clay_ball.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/gray_brick.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/gray_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/gray_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/gray_bricks.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/gray_clay.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/gray_clay_ball.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/green_brick.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/green_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/green_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/green_bricks.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/green_clay.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/green_clay_ball.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/light_blue_brick.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/light_blue_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/light_blue_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/light_blue_bricks.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/light_blue_clay.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/light_blue_clay_ball.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/light_gray_brick.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/light_gray_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/light_gray_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/light_gray_bricks.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/light_gray_clay.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/light_gray_clay_ball.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/lime_brick.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/lime_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/lime_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/lime_bricks.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/lime_clay.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/lime_clay_ball.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/magenta_brick.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/magenta_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/magenta_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/magenta_bricks.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/magenta_clay.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/magenta_clay_ball.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/orange_brick.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/orange_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/orange_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/orange_bricks.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/orange_clay.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/orange_clay_ball.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/pink_brick.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/pink_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/pink_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/pink_bricks.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/pink_clay.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/pink_clay_ball.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/purple_brick.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/purple_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/purple_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/purple_bricks.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/purple_clay.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/purple_clay_ball.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/red_brick.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/red_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/red_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/red_bricks.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/red_clay.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/red_clay_ball.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/statuary.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/white_brick.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/white_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/white_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/white_bricks.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/white_clay.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/white_clay_ball.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/yellow_brick.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/yellow_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/yellow_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/yellow_bricks.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/yellow_clay.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/items/yellow_clay_ball.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/black_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/black_brick_slab_top.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/black_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/black_brick_stairs_inner.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/black_brick_stairs_outer.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/black_bricks.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/black_clay.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/blue_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/blue_brick_slab_top.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/blue_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/blue_brick_stairs_inner.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/blue_brick_stairs_outer.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/blue_bricks.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/blue_clay.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/brown_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/brown_brick_slab_top.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/brown_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/brown_brick_stairs_inner.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/brown_brick_stairs_outer.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/brown_bricks.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/brown_clay.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/clay_workshop.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/cyan_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/cyan_brick_slab_top.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/cyan_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/cyan_brick_stairs_inner.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/cyan_brick_stairs_outer.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/cyan_bricks.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/cyan_clay.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/east_0_0_0_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/east_0_0_1_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/east_0_0_2_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/east_0_1_0_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/east_0_1_1_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/east_0_1_2_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/east_0_2_0_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/east_0_2_1_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/east_0_2_2_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/east_1_0_0_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/east_1_0_1_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/east_1_0_2_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/east_1_1_0_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/east_1_1_1_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/east_1_1_2_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/east_1_2_0_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/east_1_2_1_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/east_1_2_2_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/east_2_0_0_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/east_2_0_1_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/east_2_0_2_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/east_2_1_0_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/east_2_1_1_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/east_2_1_2_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/east_2_2_0_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/east_2_2_1_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/east_2_2_2_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/north_0_0_0_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/north_0_0_1_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/north_0_0_2_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/north_0_1_0_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/north_0_1_1_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/north_0_1_2_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/north_0_2_0_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/north_0_2_1_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/north_0_2_2_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/north_1_0_0_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/north_1_0_1_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/north_1_0_2_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/north_1_1_0_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/north_1_1_1_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/north_1_1_2_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/north_1_2_0_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/north_1_2_1_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/north_1_2_2_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/north_2_0_0_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/north_2_0_1_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/north_2_0_2_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/north_2_1_0_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/north_2_1_1_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/north_2_1_2_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/north_2_2_0_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/north_2_2_1_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/north_2_2_2_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/south_0_0_0_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/south_0_0_1_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/south_0_0_2_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/south_0_1_0_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/south_0_1_1_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/south_0_1_2_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/south_0_2_0_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/south_0_2_1_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/south_0_2_2_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/south_1_0_0_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/south_1_0_1_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/south_1_0_2_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/south_1_1_0_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/south_1_1_1_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/south_1_1_2_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/south_1_2_0_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/south_1_2_1_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/south_1_2_2_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/south_2_0_0_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/south_2_0_1_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/south_2_0_2_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/south_2_1_0_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/south_2_1_1_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/south_2_1_2_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/south_2_2_0_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/south_2_2_1_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/south_2_2_2_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/west_0_0_0_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/west_0_0_1_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/west_0_0_2_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/west_0_1_0_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/west_0_1_1_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/west_0_1_2_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/west_0_2_0_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/west_0_2_1_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/west_0_2_2_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/west_1_0_0_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/west_1_0_1_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/west_1_0_2_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/west_1_1_0_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/west_1_1_1_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/west_1_1_2_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/west_1_2_0_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/west_1_2_1_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/west_1_2_2_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/west_2_0_0_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/west_2_0_1_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/west_2_0_2_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/west_2_1_0_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/west_2_1_1_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/west_2_1_2_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/west_2_2_0_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/west_2_2_1_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/fourneau/west_2_2_2_lit.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/gray_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/gray_brick_slab_top.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/gray_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/gray_brick_stairs_inner.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/gray_brick_stairs_outer.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/gray_bricks.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/gray_clay.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/green_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/green_brick_slab_top.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/green_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/green_brick_stairs_inner.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/green_brick_stairs_outer.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/green_bricks.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/green_clay.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/light_blue_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/light_blue_brick_slab_top.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/light_blue_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/light_blue_brick_stairs_inner.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/light_blue_brick_stairs_outer.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/light_blue_bricks.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/light_blue_clay.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/light_gray_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/light_gray_brick_slab_top.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/light_gray_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/light_gray_brick_stairs_inner.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/light_gray_brick_stairs_outer.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/light_gray_bricks.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/light_gray_clay.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/lime_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/lime_brick_slab_top.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/lime_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/lime_brick_stairs_inner.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/lime_brick_stairs_outer.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/lime_bricks.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/lime_clay.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/magenta_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/magenta_brick_slab_top.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/magenta_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/magenta_brick_stairs_inner.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/magenta_brick_stairs_outer.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/magenta_bricks.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/magenta_clay.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/metatable/0.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/metatable/1.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/metatable/2.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/metatable/3.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/metatable/4.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/metatable/5.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/metatable/6.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/metatable/7.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/metatable/8.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/orange_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/orange_brick_slab_top.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/orange_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/orange_brick_stairs_inner.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/orange_brick_stairs_outer.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/orange_bricks.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/orange_clay.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/pink_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/pink_brick_slab_top.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/pink_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/pink_brick_stairs_inner.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/pink_brick_stairs_outer.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/pink_bricks.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/pink_clay.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/purple_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/purple_brick_slab_top.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/purple_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/purple_brick_stairs_inner.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/purple_brick_stairs_outer.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/purple_bricks.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/purple_clay.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/red_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/red_brick_slab_top.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/red_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/red_brick_stairs_inner.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/red_brick_stairs_outer.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/red_bricks.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/red_clay.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/statuary.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/white_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/white_brick_slab_top.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/white_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/white_brick_stairs_inner.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/white_brick_stairs_outer.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/white_bricks.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/white_clay.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/yellow_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/yellow_brick_slab_top.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/yellow_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/yellow_brick_stairs_inner.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/yellow_brick_stairs_outer.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/yellow_bricks.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/block/yellow_clay.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/item/black_brick.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/item/black_clay_ball.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/item/blue_brick.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/item/blue_clay_ball.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/item/brown_brick.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/item/brown_clay_ball.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/item/cyan_brick.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/item/cyan_clay_ball.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/item/gray_brick.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/item/gray_clay_ball.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/item/green_brick.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/item/green_clay_ball.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/item/light_blue_brick.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/item/light_blue_clay_ball.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/item/light_gray_brick.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/item/light_gray_clay_ball.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/item/lime_brick.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/item/lime_clay_ball.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/item/magenta_brick.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/item/magenta_clay_ball.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/item/orange_brick.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/item/orange_clay_ball.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/item/pink_brick.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/item/pink_clay_ball.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/item/purple_brick.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/item/purple_clay_ball.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/item/red_brick.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/item/red_clay_ball.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/item/statuary.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/item/white_brick.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/item/white_clay_ball.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/item/yellow_brick.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/models/item/yellow_clay_ball.json create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/block/black_bricks.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/block/black_clay.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/block/blue_bricks.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/block/blue_clay.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/block/brown_bricks.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/block/brown_clay.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/block/cyan_bricks.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/block/cyan_clay.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/block/fourneau/front_on.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/block/gray_bricks.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/block/gray_clay.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/block/green_bricks.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/block/green_clay.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/block/light_blue_bricks.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/block/light_blue_clay.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/block/light_gray_bricks.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/block/light_gray_clay.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/block/lime_bricks.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/block/lime_clay.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/block/magenta_bricks.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/block/magenta_clay.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/block/metatable/side.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/block/metatable/top.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/block/orange_bricks.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/block/orange_clay.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/block/pink_bricks.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/block/pink_clay.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/block/purple_bricks.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/block/purple_clay.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/block/red_bricks.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/block/red_clay.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/block/white_bricks.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/block/white_clay.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/block/yellow_bricks.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/block/yellow_clay.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/item/black_brick.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/item/black_clay_ball.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/item/blue_brick.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/item/blue_clay_ball.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/item/brown_brick.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/item/brown_clay_ball.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/item/cyan_brick.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/item/cyan_clay_ball.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/item/golden_wrench.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/item/gray_brick.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/item/gray_clay_ball.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/item/green_brick.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/item/green_clay_ball.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/item/light_blue_brick.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/item/light_blue_clay_ball.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/item/light_gray_brick.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/item/light_gray_clay_ball.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/item/lime_brick.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/item/lime_clay_ball.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/item/magenta_brick.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/item/magenta_clay_ball.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/item/orange_brick.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/item/orange_clay_ball.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/item/pink_brick.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/item/pink_clay_ball.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/item/purple_brick.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/item/purple_clay_ball.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/item/red_brick.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/item/red_clay_ball.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/item/white_brick.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/item/white_clay_ball.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/item/yellow_brick.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/item/yellow_clay_ball.png create mode 100644 mods/sanctuary/src/main/resources/assets/sanctuary/textures/misc/sculpture_white.png create mode 100644 mods/sanctuary/src/main/resources/data/minecraft/tags/block/mineable/axe.json create mode 100644 mods/sanctuary/src/main/resources/data/minecraft/tags/block/mineable/pickaxe.json create mode 100644 mods/sanctuary/src/main/resources/data/minecraft/tags/block/mineable/shovel.json create mode 100644 mods/sanctuary/src/main/resources/data/minecraft/tags/block/slabs.json create mode 100644 mods/sanctuary/src/main/resources/data/minecraft/tags/block/stairs.json create mode 100644 mods/sanctuary/src/main/resources/data/minecraft/tags/item/slabs.json create mode 100644 mods/sanctuary/src/main/resources/data/minecraft/tags/item/stairs.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/black_brick_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/black_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/black_brick_slab_from_stonecutting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/black_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/black_brick_stairs_from_stonecutting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/black_bricks.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/black_bricks_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/black_clay.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/black_clay_ball_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/black_clay_ball_smelting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/black_clay_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/black_clay_smelting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/blue_brick_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/blue_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/blue_brick_slab_from_stonecutting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/blue_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/blue_brick_stairs_from_stonecutting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/blue_bricks.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/blue_bricks_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/blue_clay.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/blue_clay_ball_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/blue_clay_ball_smelting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/blue_clay_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/blue_clay_smelting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/brown_brick_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/brown_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/brown_brick_slab_from_stonecutting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/brown_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/brown_brick_stairs_from_stonecutting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/brown_bricks.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/brown_bricks_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/brown_clay.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/brown_clay_ball_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/brown_clay_ball_smelting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/brown_clay_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/brown_clay_smelting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/cyan_brick_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/cyan_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/cyan_brick_slab_from_stonecutting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/cyan_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/cyan_brick_stairs_from_stonecutting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/cyan_bricks.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/cyan_bricks_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/cyan_clay.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/cyan_clay_ball_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/cyan_clay_ball_smelting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/cyan_clay_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/cyan_clay_smelting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/gray_brick_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/gray_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/gray_brick_slab_from_stonecutting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/gray_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/gray_brick_stairs_from_stonecutting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/gray_bricks.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/gray_bricks_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/gray_clay.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/gray_clay_ball_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/gray_clay_ball_smelting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/gray_clay_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/gray_clay_smelting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/green_brick_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/green_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/green_brick_slab_from_stonecutting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/green_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/green_brick_stairs_from_stonecutting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/green_bricks.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/green_bricks_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/green_clay.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/green_clay_ball_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/green_clay_ball_smelting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/green_clay_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/green_clay_smelting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/light_blue_brick_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/light_blue_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/light_blue_brick_slab_from_stonecutting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/light_blue_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/light_blue_brick_stairs_from_stonecutting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/light_blue_bricks.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/light_blue_bricks_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/light_blue_clay.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/light_blue_clay_ball_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/light_blue_clay_ball_smelting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/light_blue_clay_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/light_blue_clay_smelting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/light_gray_brick_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/light_gray_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/light_gray_brick_slab_from_stonecutting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/light_gray_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/light_gray_brick_stairs_from_stonecutting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/light_gray_bricks.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/light_gray_bricks_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/light_gray_clay.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/light_gray_clay_ball_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/light_gray_clay_ball_smelting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/light_gray_clay_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/light_gray_clay_smelting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/lime_brick_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/lime_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/lime_brick_slab_from_stonecutting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/lime_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/lime_brick_stairs_from_stonecutting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/lime_bricks.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/lime_bricks_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/lime_clay.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/lime_clay_ball_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/lime_clay_ball_smelting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/lime_clay_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/lime_clay_smelting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/magenta_brick_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/magenta_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/magenta_brick_slab_from_stonecutting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/magenta_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/magenta_brick_stairs_from_stonecutting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/magenta_bricks.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/magenta_bricks_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/magenta_clay.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/magenta_clay_ball_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/magenta_clay_ball_smelting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/magenta_clay_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/magenta_clay_smelting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/orange_brick_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/orange_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/orange_brick_slab_from_stonecutting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/orange_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/orange_brick_stairs_from_stonecutting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/orange_bricks.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/orange_bricks_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/orange_clay.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/orange_clay_ball_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/orange_clay_ball_smelting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/orange_clay_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/orange_clay_smelting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/pink_brick_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/pink_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/pink_brick_slab_from_stonecutting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/pink_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/pink_brick_stairs_from_stonecutting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/pink_bricks.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/pink_bricks_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/pink_clay.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/pink_clay_ball_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/pink_clay_ball_smelting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/pink_clay_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/pink_clay_smelting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/purple_brick_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/purple_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/purple_brick_slab_from_stonecutting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/purple_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/purple_brick_stairs_from_stonecutting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/purple_bricks.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/purple_bricks_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/purple_clay.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/purple_clay_ball_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/purple_clay_ball_smelting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/purple_clay_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/purple_clay_smelting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/red_brick_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/red_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/red_brick_slab_from_stonecutting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/red_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/red_brick_stairs_from_stonecutting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/red_bricks.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/red_bricks_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/red_clay.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/red_clay_ball_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/red_clay_ball_smelting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/red_clay_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/red_clay_smelting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/white_brick_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/white_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/white_brick_slab_from_stonecutting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/white_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/white_brick_stairs_from_stonecutting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/white_bricks.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/white_bricks_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/white_clay.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/white_clay_ball_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/white_clay_ball_smelting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/white_clay_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/white_clay_smelting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/yellow_brick_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/yellow_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/yellow_brick_slab_from_stonecutting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/yellow_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/yellow_brick_stairs_from_stonecutting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/yellow_bricks.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/yellow_bricks_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/yellow_clay.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/yellow_clay_ball_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/yellow_clay_ball_smelting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/yellow_clay_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/colored_bricks/yellow_clay_smelting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/advancement/recipes/decorations/clay_workshop.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/loot_table/blocks/black_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/loot_table/blocks/black_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/loot_table/blocks/black_bricks.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/loot_table/blocks/black_clay.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/loot_table/blocks/blue_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/loot_table/blocks/blue_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/loot_table/blocks/blue_bricks.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/loot_table/blocks/blue_clay.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/loot_table/blocks/brown_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/loot_table/blocks/brown_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/loot_table/blocks/brown_bricks.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/loot_table/blocks/brown_clay.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/loot_table/blocks/cyan_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/loot_table/blocks/cyan_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/loot_table/blocks/cyan_bricks.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/loot_table/blocks/cyan_clay.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/loot_table/blocks/gray_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/loot_table/blocks/gray_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/loot_table/blocks/gray_bricks.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/loot_table/blocks/gray_clay.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/loot_table/blocks/green_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/loot_table/blocks/green_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/loot_table/blocks/green_bricks.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/loot_table/blocks/green_clay.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/loot_table/blocks/light_blue_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/loot_table/blocks/light_blue_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/loot_table/blocks/light_blue_bricks.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/loot_table/blocks/light_blue_clay.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/loot_table/blocks/light_gray_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/loot_table/blocks/light_gray_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/loot_table/blocks/light_gray_bricks.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/loot_table/blocks/light_gray_clay.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/loot_table/blocks/lime_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/loot_table/blocks/lime_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/loot_table/blocks/lime_bricks.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/loot_table/blocks/lime_clay.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/loot_table/blocks/magenta_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/loot_table/blocks/magenta_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/loot_table/blocks/magenta_bricks.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/loot_table/blocks/magenta_clay.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/loot_table/blocks/orange_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/loot_table/blocks/orange_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/loot_table/blocks/orange_bricks.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/loot_table/blocks/orange_clay.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/loot_table/blocks/pink_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/loot_table/blocks/pink_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/loot_table/blocks/pink_bricks.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/loot_table/blocks/pink_clay.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/loot_table/blocks/purple_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/loot_table/blocks/purple_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/loot_table/blocks/purple_bricks.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/loot_table/blocks/purple_clay.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/loot_table/blocks/red_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/loot_table/blocks/red_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/loot_table/blocks/red_bricks.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/loot_table/blocks/red_clay.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/loot_table/blocks/white_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/loot_table/blocks/white_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/loot_table/blocks/white_bricks.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/loot_table/blocks/white_clay.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/loot_table/blocks/yellow_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/loot_table/blocks/yellow_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/loot_table/blocks/yellow_bricks.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/loot_table/blocks/yellow_clay.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/black_brick_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/black_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/black_brick_slab_from_stonecutting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/black_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/black_brick_stairs_from_stonecutting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/black_bricks.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/black_bricks_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/black_clay.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/black_clay_ball_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/black_clay_ball_smelting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/black_clay_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/black_clay_smelting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/blue_brick_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/blue_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/blue_brick_slab_from_stonecutting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/blue_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/blue_brick_stairs_from_stonecutting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/blue_bricks.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/blue_bricks_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/blue_clay.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/blue_clay_ball_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/blue_clay_ball_smelting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/blue_clay_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/blue_clay_smelting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/brown_brick_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/brown_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/brown_brick_slab_from_stonecutting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/brown_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/brown_brick_stairs_from_stonecutting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/brown_bricks.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/brown_bricks_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/brown_clay.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/brown_clay_ball_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/brown_clay_ball_smelting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/brown_clay_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/brown_clay_smelting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/clay_workshop.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/cyan_brick_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/cyan_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/cyan_brick_slab_from_stonecutting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/cyan_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/cyan_brick_stairs_from_stonecutting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/cyan_bricks.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/cyan_bricks_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/cyan_clay.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/cyan_clay_ball_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/cyan_clay_ball_smelting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/cyan_clay_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/cyan_clay_smelting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/gray_brick_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/gray_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/gray_brick_slab_from_stonecutting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/gray_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/gray_brick_stairs_from_stonecutting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/gray_bricks.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/gray_bricks_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/gray_clay.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/gray_clay_ball_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/gray_clay_ball_smelting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/gray_clay_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/gray_clay_smelting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/green_brick_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/green_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/green_brick_slab_from_stonecutting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/green_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/green_brick_stairs_from_stonecutting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/green_bricks.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/green_bricks_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/green_clay.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/green_clay_ball_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/green_clay_ball_smelting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/green_clay_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/green_clay_smelting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/light_blue_brick_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/light_blue_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/light_blue_brick_slab_from_stonecutting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/light_blue_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/light_blue_brick_stairs_from_stonecutting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/light_blue_bricks.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/light_blue_bricks_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/light_blue_clay.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/light_blue_clay_ball_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/light_blue_clay_ball_smelting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/light_blue_clay_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/light_blue_clay_smelting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/light_gray_brick_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/light_gray_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/light_gray_brick_slab_from_stonecutting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/light_gray_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/light_gray_brick_stairs_from_stonecutting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/light_gray_bricks.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/light_gray_bricks_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/light_gray_clay.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/light_gray_clay_ball_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/light_gray_clay_ball_smelting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/light_gray_clay_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/light_gray_clay_smelting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/lime_brick_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/lime_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/lime_brick_slab_from_stonecutting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/lime_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/lime_brick_stairs_from_stonecutting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/lime_bricks.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/lime_bricks_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/lime_clay.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/lime_clay_ball_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/lime_clay_ball_smelting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/lime_clay_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/lime_clay_smelting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/magenta_brick_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/magenta_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/magenta_brick_slab_from_stonecutting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/magenta_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/magenta_brick_stairs_from_stonecutting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/magenta_bricks.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/magenta_bricks_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/magenta_clay.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/magenta_clay_ball_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/magenta_clay_ball_smelting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/magenta_clay_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/magenta_clay_smelting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/orange_brick_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/orange_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/orange_brick_slab_from_stonecutting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/orange_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/orange_brick_stairs_from_stonecutting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/orange_bricks.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/orange_bricks_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/orange_clay.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/orange_clay_ball_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/orange_clay_ball_smelting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/orange_clay_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/orange_clay_smelting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/pink_brick_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/pink_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/pink_brick_slab_from_stonecutting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/pink_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/pink_brick_stairs_from_stonecutting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/pink_bricks.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/pink_bricks_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/pink_clay.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/pink_clay_ball_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/pink_clay_ball_smelting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/pink_clay_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/pink_clay_smelting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/purple_brick_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/purple_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/purple_brick_slab_from_stonecutting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/purple_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/purple_brick_stairs_from_stonecutting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/purple_bricks.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/purple_bricks_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/purple_clay.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/purple_clay_ball_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/purple_clay_ball_smelting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/purple_clay_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/purple_clay_smelting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/red_brick_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/red_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/red_brick_slab_from_stonecutting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/red_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/red_brick_stairs_from_stonecutting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/red_bricks.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/red_bricks_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/red_clay.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/red_clay_ball_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/red_clay_ball_smelting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/red_clay_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/red_clay_smelting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/white_brick_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/white_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/white_brick_slab_from_stonecutting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/white_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/white_brick_stairs_from_stonecutting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/white_bricks.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/white_bricks_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/white_clay.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/white_clay_ball_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/white_clay_ball_smelting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/white_clay_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/white_clay_smelting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/yellow_brick_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/yellow_brick_slab.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/yellow_brick_slab_from_stonecutting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/yellow_brick_stairs.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/yellow_brick_stairs_from_stonecutting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/yellow_bricks.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/yellow_bricks_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/yellow_clay.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/yellow_clay_ball_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/yellow_clay_ball_smelting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/yellow_clay_from_dye.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/recipe/yellow_clay_smelting.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/tags/item/brick_blocks.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/tags/item/brick_items.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/tags/item/clay_balls.json create mode 100644 mods/sanctuary/src/main/resources/data/sanctuary/tags/item/clay_blocks.json create mode 100644 mods/sanctuary/src/test/java/fr/koka/sanctuary/weather/Seasonal105Smoke.java create mode 100644 tools/colored-bricks-palette.json create mode 100644 tools/fourneau-embers.json create mode 100644 tools/generate-colored-bricks.py create mode 100644 tools/generate-metatable-models.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a0beab..3d5b67d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,135 @@ # Changelog +## beta.110 — Atelier d’argile inclus + +- Une seule livraison réunit l’atelier d’argile, les statuaires et l’import GLB de beta.106 avec les œufs et la neige saisonnière de beta.107 à beta.109. +- Fonctionnement, identifiants et formats conservés ; accumulation toujours plafonnée à deux blocs par défaut. +- [Contrat et vérifications](docs/clay-workshop-integration-beta110.md). + +## beta.109 — Fonte saisonnière + +- Fonte par couches des dépôts météo suivis, de haut en bas, plus rapide en été. +- Arrêt en hiver, dans les biomes froids, sous un profil neigeux ou en mode météo Vanilla. +- Constructions et neige antérieure préservées ; suivi sauvegardé par chunk, retiré lors des modifications manuelles. +- Gamerule `sanctuary:seasonal_snow_melt` active par défaut ; accumulation toujours limitée à deux blocs par défaut. +- [Contrat et essais](docs/seasonal-snow-melt-beta109.md), sur la base beta.108, Statuaire beta.106 livré séparément. + +## beta.108 — Accumulation de neige + +- Chaque précipitation ajoute une couche ; la huitième forme un bloc de neige natif solide, puis le dépôt continue au-dessus. +- Plafond de deux blocs par défaut, gamerule réglable (0 = arrêt, -1 = sans plafond particulier). +- Collisions, supports, lumière et chaudrons natifs conservés ; accumulation seulement pendant la chute de neige. +- [Contrat et essais](docs/snow-accumulation-beta108.md). Assemblage isolé sur beta.107, avec les œufs, sans le Statuaire beta.106 livré séparément. + +## beta.107 — Naissances en œufs + +- Reproduction : œuf natif au lieu d’un bébé vivant, variantes et caractéristiques héritées ; bébé garanti à l’éclosion. +- Grenouille/têtard, tortue, sniffer, allay et villageois : chemins natifs particuliers pris en charge. +- Générateur classique cassé en survie sans Toucher de soie : un œuf de son type. +- Catalogue des 88 espèces : 35 voies couvertes et 53 propositions à valider, sans ajouter ces propositions au jeu. +- [Contrat et contrôles](docs/spawn-eggs-acquisition-beta107.md), [catalogue](docs/spawn-eggs-catalogue-beta107.md). +- Livraison isolée sur la base beta.105 ; le chantier Statuaire beta.106 est conservé séparément. + +## beta.106 — Atelier d’argile et modèles 3D + +- Dossier commun pour schémas et GLB ; sélection des modèles 3D dans Statues au Métabli. +- Atelier d’argile : un bloc d’argile pour un statuaire 16³ à poser. Couleurs du modèle avec l’argile normale, couleur unie avec les seize argiles Sanctuary. +- Modèle identifié et embarqué dans l’objet, sauvegarde serveur et rendu natif de la miniature dans le monde et l’inventaire. +- Contrat d’import, de sauvegarde et limites : `docs/statuary-beta106.md`. + +## beta.105 — Saisons et chapeaux vivants + +- Molette pour les pages du fût ; panoramas avec les effets actifs du shader Sanctuary. +- Prévisions saisonnières, neige continue ou averses de neige avec accumulation native. +- Chapeaux réactifs : paratonnerre, briquet, spawner, fusée, cultures et aliments attirants. +- Livre et plume : journal personnel selon le caractère et les caresses/blessures. +- Les boules de neige activent les objets portés. Aucune Weather TNT. +- [Contrat et vérifications](docs/living-hats-seasons-beta105.md). + +## beta.104 — Contraste des argiles + +- Gris soutenu et noir plus sombre, dessin vanilla plus contrasté. +- Boules d’argile accordées aux blocs, autres textures intactes. +- Rangement beta.103 conservé ; [détails](docs/clay-contrast-beta104.md). + +## beta.103 — Rangement créatif + +- Séries de 16 par type : briques, escaliers, dalles, argiles et leurs items. +- Ordre des couleurs identique à l’onglet natif Minecraft 26.3. +- [Détails](docs/colored-bricks-order-beta103.md). + +## beta.102 — Briques et argile de couleur + +- 16 familles : briques, dalles, escaliers, argile pastel, boules d’argile et briques en items. +- Textures fournies intactes, recoloration pixel par pixel des trois textures vanilla. +- Onglets natifs Couleurs/Ingrédients, recettes, cuisson, tailleur, butin et découvertes. +- [Contrat et vérifications](docs/colored-bricks-beta102.md). + +## beta.101 — Atterrissage des familiers volants + +- Les profils volants ne subissent plus les dégâts et effets de chute de leur entité commune. +- Les vrais coups et les chutes des familiers terrestres sont conservés. +- [Reproduction et vérifications](docs/flying-landing-beta101.md). + +## beta.100 — Exploration et promenade des familiers + +- Statues limitées aux espèces rencontrées, vaincues ou ayant tué le joueur. +- Catalogue et génération filtrés par une liste de découvertes fournie par le serveur. +- Promenade avec destinations stables et pauses selon le tempérament, y compris en vol et en garde. +- Import `.schem` : conversion native des anciennes palettes dont la version est connue, diagnostic du bloc incompatible. +- [Contrat et vérifications](docs/exploration-familiers-beta100.md). + +## beta.099 — Catalogue, compétences et gestes K + +- Compétences avant les progrès ; arbre natif intégré et suivi par clic droit. +- Fiches illustrées communes, trois plans récents terminés, infrastructures et décoration. +- Palette de statue étendue aux blocs du registre, avec comparaison couleur et forme. +- K bref : ancrer au point visé ou tourner au même point ; K maintenu : commandes. +- Métabli préassemblé sur le piédestal des nouvelles salles souterraines. +- Clé dorée en outil plat, texture vanilla provisoire en attente de celle du créateur. +- [Contrat et vérifications](docs/catalogue-progression-beta099.md). + +## beta.098 — Construction créative + +- Bouton « Construire le plan… » dans K, visible uniquement en créatif. +- Confirmation du plan complet à l’origine et dans l’orientation sélectionnées. +- Statues, bâtiments et composants de machines ; assemblage multibloc à la clé. +- [Détails et vérifications](docs/creative-construction-beta098.md). + +## beta.097 — Temples et aperçu texturé + +- Recherche de secours pour les temples des expéditions, avec pièces vanilla et fondation locale si nécessaire. +- Conserve la première recherche et les emplacements déjà admissibles. +- Aperçu des plans avec les modèles et textures des blocs, formes natives et nom du matériau visé. +- [Contrat et vérifications](docs/temple-crash-beta097.md). + +## beta.096 — Métabli + +- Atelier de neuf établis avec les textures fournies, assemblage et dissociation à la clé. +- Catalogue Statues, Machines, Bâtiments et Mes plans ; sélection unique à l’atelier. +- K garde les commandes de placement, rotation, couches et matériaux. +- Construction manuelle ; changer de projet ou l’abandonner exige de revenir à l’atelier. +- [Détails et vérifications](docs/metabli-beta096.md). + +## beta.095 — Pilier des super pistons + +- Bois du centre de la grande plaque sur la tige, sans bordure ni étirement. +- Découpage adapté aux segments courts du socle et de la tête. +- [Détails et vérifications](docs/piston-shaft-beta095.md). + +## beta.094 — Feu du Fourneau + +- Braises dans les deux ouvertures, allumage commun lié à la chaleur. +- Flammes et fumée alignées sur la façade dans les quatre orientations. +- Pierre originale et fours isolés conservés. +- [Détails et vérifications](docs/fourneau-allume-beta094.md). + +## beta.093 — Crash des infobulles pendant la recherche + +- Retire les accès à la police graphique lors de l’indexation asynchrone des + descriptions de familiers ; texte complet et mise en page visible conservés. +- [Diagnostic et vérifications](docs/tooltip-crash-beta093.md). + ## beta.092 — Clic molette des super pistons - Socles, têtes et tiges renvoient le piston normal ou collant d’origine. diff --git a/README.md b/README.md index 1c4d24c..998e155 100644 --- a/README.md +++ b/README.md @@ -20,10 +20,10 @@ La [vision complète](docs/vision.md) conserve les intentions ; le L’économie, les claims et les dimensions décrits dans la vision restent à implémenter. -Les sources Git sont synchronisées jusqu’à **beta.092**. Le récapitulatif de -[synchronisation](docs/git-sync-beta092.md) précise les vérifications et les -limites. Les archives bêta mentionnées dans ce document sont des fichiers -locaux ; la dernière release du canal packwiz reste **alpha.30.7**. +La version **beta.110** réunit les développements jusqu’à l’atelier d’argile, +aux œufs et à la neige saisonnière. Le [suivi de livraison](docs/clay-workshop-integration-beta110.md) +précise les vérifications, la publication et la synchronisation de l’instance. +Les [synchronisations antérieures](docs/git-sync-beta092.md) restent documentées. Les [archives pour le site](archives/web/README.md) conservent la maquette Blocodex autonome, les documents et les images retrouvées jusqu'à alpha.30.7. @@ -31,6 +31,161 @@ La [carte native beta.005](docs/atlas-beta005.md) prend le relais après l’étude de Xaero et des alternatives libres. +## beta.110 — Atelier d’argile et neige saisonnière réunis + +Le Clay Workshop / atelier d’argile, les statuaires et les modèles 3D rejoignent +les œufs, l’accumulation et la fonte saisonnière dans un seul pack. +[Contrat et vérifications](docs/clay-workshop-integration-beta110.md). +[Pack normal](build/Sanctuary-beta.110.mrpack) · +[Pack de test](build/Sanctuary-Test-beta.110.mrpack). + +## beta.109 — Fonte saisonnière + +La neige météo fond progressivement hors hiver, avec un rythme adapté à la +saison. Les constructions en neige sont préservées ; accumulation plafonnée +à deux blocs par défaut. [Contrat et essais](docs/seasonal-snow-melt-beta109.md). +Base beta.108, Statuaire beta.106 livré séparément. +[Pack normal](build/Sanctuary-beta.109.mrpack) · +[Pack de test](build/Sanctuary-Test-beta.109.mrpack). + +## beta.108 — Neige en volume + +Huit couches deviennent un bloc de neige solide ; les dépôts se poursuivent +au-dessus. Hauteur par défaut de deux blocs, réglable par gamerule. +[Contrat et contrôles](docs/snow-accumulation-beta108.md). +Base beta.107 conservée, avec ses œufs ; Statuaire beta.106 livré séparément. +[Pack normal](build/Sanctuary-beta.108.mrpack) · +[Pack de test](build/Sanctuary-Test-beta.108.mrpack). + +## beta.107 — Œufs de reproduction et de générateurs + +Les naissances deviennent des œufs transportables, avec bébé et héritage natif. +Les générateurs classiques donnent leur œuf lorsqu’ils sont cassés sans Toucher +de soie. [Détails](docs/spawn-eggs-acquisition-beta107.md) · +[Catalogue des 88 espèces à valider](docs/spawn-eggs-catalogue-beta107.md). +Assemblage isolé sur la base beta.105, pendant le chantier Statuaire beta.106. +[Pack normal](build/Sanctuary-beta.107.mrpack) · +[Pack de test](build/Sanctuary-Test-beta.107.mrpack). + +## beta.106 — Atelier d’argile et modèles 3D + +Un même dossier pour schémas et fichiers GLB. Le Métabli les transforme en +grandes statues en blocs ; l’atelier d’argile fabrique des statuaires 16³ +transportables et posables. Argile normale : couleurs d’origine ; argile +colorée : sculpture unie. [Contrat et validation](docs/statuary-beta106.md). +[Pack normal](build/Sanctuary-beta.106.mrpack) · +[Pack de test](build/Sanctuary-Test-beta.106.mrpack). + +## beta.105 — Saisons et chapeaux vivants + +Molette dans le fût, panoramas avec le shader Sanctuary, prévisions saisonnières +et neige au sol. Les mobs équipés utilisent leurs objets, attirent les animaux +et écrivent leur journal. [Détails](docs/living-hats-seasons-beta105.md). +[Pack normal](build/Sanctuary-beta.105.mrpack) · +[Pack de test](build/Sanctuary-Test-beta.105.mrpack). + +## beta.104 — Argiles grise et noire + +Teintes plus distinctes et contraste renforcé, sur les blocs et leurs items. +Le rangement par séries de 16 est conservé. [Détails](docs/clay-contrast-beta104.md) · +[Pack normal](build/Sanctuary-beta.104.mrpack) · +[Pack de test](build/Sanctuary-Test-beta.104.mrpack). + +## beta.103 — Rangement par séries de 16 + +Briques, escaliers, dalles et argiles regroupés par type, dans l’ordre des +couleurs natif. [Détails](docs/colored-bricks-order-beta103.md) · +[Pack normal](build/Sanctuary-beta.103.mrpack) · +[Pack de test](build/Sanctuary-Test-beta.103.mrpack). + +## beta.102 — Briques et argile de couleur + +Seize couleurs : briques, dalles, escaliers, argile pastel et leurs items. +Blocs dans l’onglet natif des couleurs ; recettes et textures 16 × 16. +[Détails](docs/colored-bricks-beta102.md) · +[Pack normal](build/Sanctuary-beta.102.mrpack) · +[Pack de test](build/Sanctuary-Test-beta.102.mrpack). + +## beta.101 — Atterrissage des familiers volants + +Les familiers volants ne prennent plus de coup de chute au contact du sol. +Les vrais dégâts de combat restent actifs. +[Validation](docs/flying-landing-beta101.md) · +[Pack normal](build/Sanctuary-beta.101.mrpack) · +[Pack de test](build/Sanctuary-Test-beta.101.mrpack). + +## beta.100 — Exploration et familiers + +Les statues se débloquent par rencontre ou combat. Les familiers se promènent +avec des pauses selon leur tempérament ; les volants ne tournent plus en orbite +permanente. [Détails et validation](docs/exploration-familiers-beta100.md). + +[Pack normal](build/Sanctuary-beta.100.mrpack) · +[Pack de test](build/Sanctuary-Test-beta.100.mrpack). + +## beta.099 — Catalogue du Métabli et Progression + +Compétences en haut, arbre des progrès directement dessous. Le Métabli propose +un catalogue à aperçus, les trois derniers plans terminés et une palette étendue +pour les statues. K bref place/tourne, K maintenu ouvre les commandes. +Les nouvelles grandes salles souterraines accueillent un Métabli sur leur piédestal. +[Détails et vérifications](docs/catalogue-progression-beta099.md). +[Pack normal](build/Sanctuary-beta.099.mrpack) · +[Monde plat rapide](build/Sanctuary-Test-beta.099.mrpack). + +## beta.098 — Construire le plan en créatif + +Après sélection au Métabli, K propose **Construire le plan…** uniquement en +créatif. Statues, bâtiments et composants de machines sont posés selon +l’origine et l’orientation choisies, après confirmation. +[Détails et vérifications](docs/creative-construction-beta098.md). +[Pack normal](build/Sanctuary-beta.098.mrpack) · +[Monde plat rapide](build/Sanctuary-Test-beta.098.mrpack). + +## beta.097 — Temples et aperçu texturé + +Corrige le chargement de certaines graines sans emplacement naturel de temple : +recherche élargie, puis présence forcée des pièces vanilla avec fondation locale +si nécessaire. Les aperçus de construction utilisent les modèles et textures +des blocs du pack actif et indiquent le matériau visé. +[Détails et vérifications](docs/temple-crash-beta097.md). +[Pack normal](build/Sanctuary-beta.097.mrpack) · +[Monde plat rapide](build/Sanctuary-Test-beta.097.mrpack). + +## beta.096 — Métabli et construction guidée + +Neuf établis à plat, assemblés à la clé, ouvrent le catalogue de statues, +machines, bâtiments et plans personnels. Un choix ferme le catalogue ; K +permet ensuite de placer et d’orienter l’aperçu. Changer ou abandonner le projet +se fait au Métabli. Construction manuelle avec ses matériaux. +[Détails et vérifications](docs/metabli-beta096.md). +[Pack normal](build/Sanctuary-beta.096.mrpack) · +[Monde plat rapide](build/Sanctuary-Test-beta.096.mrpack). + +## beta.095 — Texture du pilier des super pistons + +La tige centrale utilise uniquement le bois au centre de la grande plaque, +y compris ses segments courts dans le socle et la tête. +[Détails et vérifications](docs/piston-shaft-beta095.md). +[Pack normal](build/Sanctuary-beta.095.mrpack) · +[Monde plat rapide](build/Sanctuary-Test-beta.095.mrpack). + +## beta.094 — Fourneau allumé + +Les deux foyers brillent pendant la chauffe ; flammes et fumée sortent des +ouvertures de la façade. La pierre d’origine est conservée. +[Détails et vérifications](docs/fourneau-allume-beta094.md). +[Pack normal](build/Sanctuary-beta.094.mrpack) · +[Monde plat rapide](build/Sanctuary-Test-beta.094.mrpack). + +## beta.093 — Crash de recherche créative + +Les descriptions de familiers peuvent être indexées en arrière-plan sans accéder +à la police graphique. Leur mise en page reste conservée à l’affichage. +[Diagnostic et vérifications](docs/tooltip-crash-beta093.md). +[Pack normal](build/Sanctuary-beta.093.mrpack) · +[Monde plat rapide](build/Sanctuary-Test-beta.093.mrpack). + ## beta.092 — Clic molette des super pistons Viser un socle, une tête ou une tige sélectionne le piston normal ou collant @@ -1896,7 +2051,7 @@ Depuis beta.090, la cible est **Minecraft 26.3 finale**, publiée le 15 septembr | Fabric API | 0.160.5+26.3 | | Fabric Loom | 1.17.20 | | Gradle Wrapper | 9.5.1, distribution vérifiée par SHA-256 | -| Sanctuary / pack | beta.092 | +| Sanctuary / pack | beta.101 | Java 25 et Python 3.11 ou plus récent sont nécessaires. Le script pack utilise uniquement la bibliothèque standard et repère aussi une installation Python diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index 90abc95..747f161 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -117,3 +117,13 @@ commit `62513191f6a3497447595c2215d466ad1d2bdb92`, sous Unlicense. Le comportement des contours et leurs coordonnées d’atlas suivent cette source ; l’intégration, la synchronisation et le calcul de capacité sont propres à Sanctuary. Texte de licence : [Unlicense amont](https://github.com/squeek502/AppleSkin/blob/62513191f6a3497447595c2215d466ad1d2bdb92/LICENSE). + +## Briques et argile beta.102 + +Les seize textures de blocs de briques ont été fournies par le créateur de +Sanctuary. Les variantes d’argile, de boule d’argile et de brique sont des +recolorations des textures Minecraft 26.3 de Mojang/Microsoft. Les ressources +de modèles, butin et états reprennent leurs équivalents natifs 26.3. +`tools/generate-colored-bricks.py` documente cette transformation ; +`tools/colored-bricks-palette.json` conserve couleurs et empreintes des PNG fournis. +Ces ressources dérivées ne sont pas présentées comme des créations originales. diff --git a/docs/backlog.md b/docs/backlog.md index 454463d..f4edb22 100644 --- a/docs/backlog.md +++ b/docs/backlog.md @@ -1,5 +1,32 @@ # Backlog Sanctuary +## STAT-01 — Atelier d’argile et modèles 3D — beta.106 + +Branche `codex/statuary-import-beta106`. [Contrat et validation](statuary-beta106.md). +Dossier commun, GLB au Métabli pour les statues en blocs, atelier d’argile +pour les miniatures 16³ avec consommation native, teinte et identité du modèle. +Cycle natif de fabrication/pose/récupération/rechargement vérifié, y compris +sans fichier source. Build complet et archives normale/Test vérifiés localement. + +## BUILD-104 — Contraste des argiles + +Branche `codex/clay-contrast-beta104`. [Ticket](clay-contrast-beta104.md). +Gris et noir plus distincts ; blocs et items accordés. Aperçu, contraste, +transparence, build complet et archives normal/Test vérifiés. + +## BUILD-103 — Rangement créatif par séries + +Branche `codex/colored-bricks-order-beta103`. [Ticket](colored-bricks-order-beta103.md). +Groupes de seize par type et ordre de couleurs natif. Build complet et +archives normal/Test vérifiés ; ressources beta.102 inchangées. + +## BUILD-102 — Briques et argile de couleur + +Branche `codex/colored-bricks-beta102`. [Ticket](colored-bricks-beta102.md). +Seize familles natives et textures recolorées à partir des originaux vanilla. +Parcours natif, recettes, butin, modèles, sauvegarde/réouverture, build complet +et archives normal/Test vérifiés. + ## SKY-077 — tourbillon au-dessus du vide Branche `codex/cloud-vortex-beta077`. [Ticket](cloud-vortex-beta077.md). @@ -40,6 +67,18 @@ Capture native des 88 espèces, parcours creeper créatif/survie FR/EN, interopérabilité Litemapy, sept tests serveur et 130 tâches Gradle réussis. Archives normal/Test vérifiées ; anciennes `.schematic` à convertir en externe. +## BUILD-02 — Métabli et menu de construction + +**Premier lot implémenté en beta.096**, branche `codex/metabli-beta096`. +[Contrat et vérifications](metabli-beta096.md) · +[Conception et extension future du catalogue](metabli-construction.md). +Neuf établis à plat ouvrent le catalogue ; choix d’un projet à l’atelier, +placement et orientation avec K. Statues, quatre machines existantes, abri, +passerelle et imports ; construction manuelle guidée, sans bouton pause ajouté. +Changer/importer/abandonner exige le Métabli. Projet local à la session actuelle, +exportable ; chantiers partagés persistants, catalogue serveur en datapack et +fonctions commerciales restent à réaliser. + ## MB-01 — Reprendre le catalogue des multiblocs **Ticket local ouvert le 15 septembre 2026 ; premier lot implémenté en beta.084.** @@ -1726,3 +1765,86 @@ Voir [la génération](generation-alpha30.5.md) et [l’atlas](terrain-atlas.md) Livré et vérifié : trois modes de combat, inscriptions sans plafond pour les arènes, paris en objets, annonces et liste publique. Joueurs avec morts réelles. [Contrat et essais](arenas-beta073.md). + +## EXP-03 — Crash de recherche de temple + +Correctif local beta.097 sur `codex/expedition-temple-crash-beta097`. +[Contrat et vérifications](temple-crash-beta097.md). Graine du rapport +`-4700804240597771092` ; le créateur demande de garantir le temple plutôt que +d’omettre le bâtiment ou de refuser la graine. Les plans de construction gagnent +aussi leur aperçu texturé à sa demande pendant ce correctif. + +## CONSTRUCTION-098 — construction directe en créatif + +Branche `codex/creative-construction-beta098`. +[Ticket](creative-construction-beta098.md) : bouton K créatif, confirmation et +placement serveur du projet choisi au Métabli ; parcours manuel en survie. +Essai client natif (statue, machine, bâtiment, annulation et contrôles créatif), +build complet et archives normal/Test vérifiés. + +## CATALOGUE-099 — Progression et Métabli illustré + +Branche `codex/catalogue-progression-beta099`. +[Contrat](catalogue-progression-beta099.md) : arbre des progrès intégré sous +les compétences, catalogue partagé entre catégories avec aperçus et trois +plans personnels terminés, palette étendue, K bref/maintenu et Métabli dans +les nouvelles salles souterraines. Anciennes pièces sauvegardées préservées. + +## EXPLORATION-100 — Statues découvertes et promenade + +Branche `codex/exploration-familiers-beta100`. +[Contrat](exploration-familiers-beta100.md) : catalogue de statues basé sur les +rencontres et statistiques serveur ; promenade avec pauses selon le tempérament, +sans orbite permanente ni concurrence avec les ordres et le combat. + +EXPLORATION-100 vérifié : douze promenades, régression autonomie/ordres, catalogue +serveur, imports `.schem`, réouverture et deuxième monde ; build et archives +normal/Test réussis. Incident Windows non reproduit sans son journal. + +## LANDING-101 — Atterrissage des familiers volants + +Branche `codex/flying-landing-beta101`. +[Ticket](flying-landing-beta101.md) : supprimer le faux coup de chute des +profils volants, conserver les attaques réelles et les chutes terrestres. + +LANDING-101 vérifié : reproduction native avant correctif, douze espèces +volantes, contacts lents/rapides, vraies attaques et chute terrestre ; +build complet et archives normal/Test contrôlés. + +## beta.105 — Saisons et chapeaux vivants + +Livré localement et vérifié en copie isolée : [contrat et contrôles](living-hats-seasons-beta105.md). + +## EGGS-107 — Acquisition des œufs + +Branche `codex/eggs-beta107`. [Contrat](spawn-eggs-acquisition-beta107.md) : +naissances en œufs et œuf du générateur sans Toucher de soie. +[Catalogue](spawn-eggs-catalogue-beta107.md) : 88 espèces, 35 voies couvertes, +53 propositions en attente de validation du créateur. Les rangs et Anomaly +restent hors de ce ticket. Les sources sont intégrées en préservant le chantier +Statuaire ; la validation binaire est isolée sur la dernière base livrée beta.105. + +## SNOW-108 — Neige en volume + +Branche `codex/snow-accumulation-beta108`. +[Contrat](snow-accumulation-beta108.md) : épaississement par précipitations, +blocs pleins à huit couches et accumulation verticale. Limite serveur de deux +blocs par défaut, réglable. Les blocs sont natifs, sans migration de terrain. +Livraison locale vérifiée : essais natifs dans deux mondes neufs, build complet +et archives normal/Test contrôlés ; assemblage isolé sur beta.107. + +## SNOW-109 — Fonte saisonnière + +Branche `codex/seasonal-snow-melt-beta109`. +[Contrat](seasonal-snow-melt-beta109.md) : fonte progressive par saison, +provenance des dépôts persistante par chunk, constructions préservées. +Livraison locale vérifiée sur beta.108 : essais natifs, rechargement de +chunk, build complet et archives normal/Test contrôlés. + +## INTEGRATE-110 — Atelier d’argile dans le pack complet + +Branche `codex/clay-workshop-integration-beta110`. +[Ticket](clay-workshop-integration-beta110.md) : assemblage commun de +beta.106 et beta.109, avec tous les systèmes intermédiaires. Essais natifs de +l’atelier, des œufs et de la neige, build et archives réussis. Publication +et synchronisation de l’instance demandées ensuite, en cours. diff --git a/docs/catalogue-progression-beta099.md b/docs/catalogue-progression-beta099.md new file mode 100644 index 0000000..bdd9289 --- /dev/null +++ b/docs/catalogue-progression-beta099.md @@ -0,0 +1,127 @@ +# beta.099 — Progression, catalogue du Métabli et gestes K + +Contrat de réalisation, 16 septembre 2026. Branche `codex/catalogue-progression-beta099`. + +- Compétences en haut de la colonne gauche, arbre des progrès Minecraft intégré + dessous ; aptitudes à droite. Prestige et historique sous l’arbre. +- Catalogue visuel natif : trois derniers plans terminés en accueil, statues, + machines, bâtiments, infrastructures, décoration et bibliothèque. Même format + de fiches (aperçu, nom, dimensions, description et sélection). +- Historique personnel local de trois plans terminés, sans autorité sur le monde : + aucun choix de plan n’est compté comme construction et chaque placement reste + contrôlé par le serveur. Le stockage est distinct des sauvegardes Minecraft. +- Palette de statue étendue aux blocs ayant un objet utilisable, échantillonnée + depuis le pack actif. Pas de blocs techniques sans objet ni de commandes. +- Clé dorée : modèle plat d’outil Minecraft. Texture provisoire vanilla, à + remplacer par la texture que le créateur fournira ; aucune texture dessinée + à sa place. +- K bref ancre au bloc visé ; rappuyer sur le même point tourne de 90 degrés. + K maintenu ouvre le cockpit. Viser le vide ne repositionne pas le projet. + +## Contrat de génération et de compatibilité + +Le module EXPANSION_HALL conserve sa géométrie, ses appuis, son piédestal et son +identifiant. Les nouvelles pièces natives de la salle indépendante portent un +champ optionnel `SanctuaryMetatable099=true` : neuf parties de Métabli assemblées +sont posées au centre, un bloc au-dessus du piédestal. Une pièce enregistrée sans +ce champ conserve son comportement ancien (aucun ajout), même si un de ses chunks +n’était pas encore décoré. Le champ ne modifie ni le journal ni la graine ni les +identifiants de génération. Aucune régénération ou écriture dans un monde +personnel existant. Les nouvelles salles générées après cette version reçoivent +l’exemple ; les salles déjà enregistrées ne sont pas modifiées. + +Une fois généré, le Métabli suit les règles normales : le casser le désassemble, +et le reconstruire exige la clé dorée. Pas de réparation automatique au chargement. +Vérification prévue sur nouvelles sauvegardes de développement et round-trip NBT +avec et sans le champ optionnel, génération en plusieurs ordres de chunks. + +## Validation + +Deux essais natifs sur Minecraft **26.3 finale**, nouveaux mondes de développement : + +- `Catalogue099ClientChecks` : **réussi en 1 min 33 s**. Génération et construction + d’une statue de Creeper de 32 blocs (1 542 cellules) avec la palette étendue ; + construction des plans Fourneau et Abri, confirmation/annulation, retrait du + créatif pendant la confirmation et refus d’un envoi falsifié en survie. Pose + manuelle via le contrôleur Minecraft consommant exactement un bloc. Trois plans + terminés mémorisés, conservés au changement de monde, écrits sur disque et + décodés. Catalogue physique, catégories, aperçus, import/export, abandon et + destruction du Métabli ; K bref/rotation/visée vide/maintien. Arbre des progrès + sous les compétences, clic droit passant par l’écran et activant réellement le + suivi. Interfaces FR/EN, échelles 2 et 3, petite fenêtre et fenêtre 1280 × 800. +- `Temple097ClientChecks`, étendu à la salle : **réussi en 4 min 48 s**, graine + **-4700804240597771092**, génération racine `sanctuary:island_v24`. Origine des + neuf parties du Métabli : **(-56, 169, -117)**, sur le piédestal de pierre lisse. + Les neuf états `part=0..8` sont présents après génération puis après sauvegarde + et réouverture du monde de développement. Aller-retour NBT du nouveau champ ; + ancien tag sans champ restant sans champ. Régression des deux temples natifs, + coffres, pièges et cache de planification réussie. + +Les captures d’interface vérifiées sont dans `build/catalog099-evidence/`. +Journaux : `build/catalog099-client.log` et `build/catalog099-hall.log`. +La capture de la salle prise immédiatement après téléportation précédait le +rendu des chunks et n’est pas utilisée comme preuve visuelle ; les assertions +sur les blocs, appuis et sauvegardes constituent la validation de sa génération. + +Les premiers passages ont détecté deux problèmes corrigés : les petits blocs +(fleurs) favorisés par une comparaison de couleur seule, et la protection native +des vestiges refusant l’ajout du Métabli hors de leur contexte de génération. +Des clics automatisés immédiatement après redimensionnement donnaient aussi un +rayon de caméra périmé : les interactions physiques répétées du test passent +maintenant par le contrôleur client Minecraft et son protocole normal, avec +contrôle serveur de la consommation et de l’ouverture. Les gestes K et le suivi +des progrès restent testés par les événements d’entrée natifs. + +`check build assemblePack assembleTestPack` : **réussi en 3 min 7 s**, 124 tâches. +Le GameTest sur serveur dédié est exclu ; les tests client natifs ci-dessus +utilisent uniquement les mondes de développement. + +Les deux exports packwiz sont vérifiés : version, sources Java, contenu du JAR, +absence des classes de test et conservation des archives beta.098. Reçu complet : +`build/beta099-artifact.json`. + +| Archive | Octets | SHA-256 | +| --- | ---: | --- | +| `Sanctuary-beta.099.mrpack` | 9 908 941 | `9722e2410e18bba34cf5db835facec4bb29fc3bd7f071942e7c8321a60d955ed` | +| `Sanctuary-Test-beta.099.mrpack` | 9 927 866 | `7ce02718c2e4c780197f9984b1ac7bc7a443f78eaa3e7d8ea248aa6b2e75745a` | + +Aucune publication du canal ni installation personnelle effectuée. + +## Détails du catalogue + +Les vignettes de plans utilisent les quads, UV et textures du pack actif. +Les blocs rendus par un moteur spécial sans quads (certains conteneurs) ont une +silhouette issue de leur forme. Les vignettes sont mises en cache (48 au maximum), +invalidées lors d’un rechargement du pack. Les échantillons sont bornés à 64 × 64 +par texture et prennent la première image des textures animées. +Les statues du catalogue affichent le modèle natif adulte ; le choix ouvre les +réglages de hauteur et de palette avant génération. Sur les petites fenêtres, +la description et les dimensions passent dans l’infobulle pour garder le choix +visible sous la vignette. + +La palette « Tous les blocs » parcourt les blocs enregistrés ayant un objet, +sans inclure les blocs techniques sans objet ou les blocs de commande. La +comparaison de couleur utilise Oklab ; un coût de forme favorise les volumes +pleins, et les blocs soumis à la gravité sont moins favorisés. Les feuilles +utilisent leur état persistant, comme après une pose par un joueur. Les palettes +mixte, laine et béton restent disponibles. + +L’historique `schematics/sanctuary/recent-completed.nbt` contient au maximum +trois plans distincts, écrits atomiquement hors du fil de rendu. Il enregistre +une transition d’incomplet à complet observée dans le monde, en créatif ou en +survie. Sélectionner un plan, l’apercevoir ou l’envoyer au serveur ne suffit pas. +Il s’agit d’un historique personnel de cette installation, pas d’un catalogue +partagé par le serveur. Aucun inventaire ou entité n’est enregistré. + +K bref utilise un rayon de 96 blocs sur les blocs du monde. La case adjacente à +la face visée sert de centre d’ancrage horizontal. Une seconde pression sur ce +même point tourne autour de cet ancrage ; déplacer le regard vers un autre +point déplace le plan. Le maintien de 10 ticks ouvre les commandes sans exécuter +le geste bref à la relâche. Les raccourcis remappés utilisent la même logique. + +## Texture de la clé + +Le modèle est `minecraft:item/handheld` ; la texture provisoire est une copie +de la pioche dorée vanilla. Le créateur pourra remplacer uniquement +`mods/sanctuary/src/main/resources/assets/sanctuary/textures/item/golden_wrench.png` +par son propre PNG. Aucune illustration personnalisée n’a été créée à sa place. diff --git a/docs/clay-contrast-beta104.md b/docs/clay-contrast-beta104.md new file mode 100644 index 0000000..e3950ed --- /dev/null +++ b/docs/clay-contrast-beta104.md @@ -0,0 +1,41 @@ +# beta.104 — Contraste des argiles grise et noire + +Branche `codex/clay-contrast-beta104`, Minecraft 26.3. + +Le gris devient plus soutenu et le noir plus sombre, pour distinguer clairement +les trois argiles gris clair/gris/noir. Les nuances du dessin vanilla sont +renforcées : facteur 1,35 au lieu de 0,85. Le mélange conserve davantage de la +couleur des briques fournies (80 % pour le gris, 90 % pour le noir). + +Les textures de bloc et de boule d’argile sont accordées, toujours en 16 × 16 +avec les silhouettes et transparences vanilla. La recoloration exacte par +script reste la méthode choisie par le créateur. Les quatorze autres couleurs +et toutes les briques restent identiques. Le rangement beta.103 est conservé. + +[Aperçu avant/après](../build/clay-contrast-beta104-preview.png). +Le générateur écrit désormais son aperçu au nom de la version courante, +pour préserver les aperçus historiques. + +## Validation et livraison + +`check build assemblePack assembleTestPack -x :sanctuary:runGameTest` réussi +en 2 min 22 s (124 tâches). Comparaison visuelle avant/après effectuée. +Pas de nouvelle session Minecraft pour cette modification de textures. + +La vérification des archives confirme exactement quatre textures modifiées : +blocs et boules d’argile gris/noir. Toutes les classes Java, recettes, modèles, +autres assets et anciennes archives beta.103 sont inchangés. +Les images conservent leur taille 16 × 16 et leur transparence pixel par pixel. +Le contraste mesuré augmente de plus de 40 % sur chacune des quatre textures. +Luminance moyenne des blocs : gris clair 168,4 ; gris 118,0 ; noir 68,4. +Sources, métadonnées et contenu des packs vérifiés ; `git diff --check` réussi. +Reçu : `build/beta104-artifact.json`. + +- [Pack normal](../build/Sanctuary-beta.104.mrpack), SHA-256 : + `83f087cce41316eed620e9764ff0b643c3d5af40fa5b4cebdff5720740708631`. +- [Pack de test](../build/Sanctuary-Test-beta.104.mrpack), SHA-256 : + `adbaad161550ab80dcbc88c27d760e2f735eef9a69ba3f9e2e69bbd25e895147`. + +Les GameTests dédiés restent exclus. Aucun monde, canal ou installation +personnelle modifié ; archives locales uniquement. + diff --git a/docs/clay-workshop-integration-beta110.md b/docs/clay-workshop-integration-beta110.md new file mode 100644 index 0000000..5407a49 --- /dev/null +++ b/docs/clay-workshop-integration-beta110.md @@ -0,0 +1,97 @@ +# INTEGRATE-110 — Atelier d'argile dans la version complète + +Demande du 17 septembre 2026 : inclure le Clay Workshop dans la version avec +la fonte saisonnière. Branche `codex/clay-workshop-integration-beta110`. +État : assemblage commun vérifié, publication et synchronisation en cours. + +## Périmètre et compatibilité + +La beta.110 réunit le Clay Workshop / atelier d'argile, le Statuaire et l'import +GLB de beta.106 avec les œufs de beta.107, la neige en volume de beta.108 et +la fonte saisonnière de beta.109. Aucune fonction retirée de ces livraisons. +Le Métabli conserve son import de modèles 3D en plans de construction. + +Les sources du dossier principal correspondent déjà à cette union : les +30 fichiers de production du Statuaire sont ceux de la livraison beta.106 ; +les traductions FR/EN et listes de mixins sont la réunion exacte des deux bases, +sans conflit de valeur ni doublon. Les autres sources restent celles de beta.109. +Les fichiers Finder `.DS_Store` sont exclus par la configuration Gradle existante. + +Les identifiants et contrats de données restent ceux des tickets existants : +[Statuaire](statuary-beta106.md), [œufs](spawn-eggs-acquisition-beta107.md), +[accumulation](snow-accumulation-beta108.md), +[fonte](seasonal-snow-melt-beta109.md). Aucun format changé, aucune migration, +aucune génération ou expansion activée. Les tests créent des mondes de +laboratoire et rouvrent seulement leurs propres sauvegardes jetables. + +Les compteurs `mod_version`, `pack_version` et le manifeste packwiz passent +ensemble à beta.110. Les archives beta.106 à beta.109 restent immuables. +Le créateur a ensuite demandé de tout mettre à jour : publication de la +version vérifiée sur le canal stable puis synchronisation de l’instance +Sanctuary Beta existante, après sauvegarde des fichiers gérés. + +## Validation exécutée + +Réutilisation des essais natifs de l'atelier (fabrication, rendu, Métabli et +sauvegarde/rechargement), des œufs (reproduction, éclosion et générateurs), et +de la neige (accumulation et fonte selon les saisons, provenance et rechargement) +sur les mêmes sources réunies. Puis `check build assemblePack assembleTestPack`. +Le GameTest dédié reste exclu conformément au refus antérieur de son EULA ; +les tests natifs tournent avec le serveur intégré. + +## Contrat de mise à jour de l'instance existante + +Cible unique : `Sanctuary-0.1.0-alpha.1`, nom affiché **Sanctuary Beta**, déjà +reliée au canal `sanctuary-beta/packwiz`. Les instances historiques 26.2 et les +instances importées séparément restent hors de cette synchronisation. + +Minecraft 26.3-pre-2 passe à 26.3 finale, Fabric Loader reste en 0.19.5, +Fabric API passe de 0.160.0+26.3 à 0.160.5+26.3 ; Java 25 est déjà configuré. +L'installateur packwiz est chargé de remplacer les deux JAR suivis et d'ajuster +les composants de lancement. Sauvegarde préalable des JAR gérés, du suivi +packwiz, de `instance.cfg` et de `mmc-pack.json` hors de `mods/`. + +Aucun monde personnel ne sera ouvert, converti, régénéré ou exploré pendant +l'opération. Les fichiers de sauvegarde, réglages, captures et packs personnels +sont comparés par empreintes avant/après. Seuls les composants gérés du pack et +la version du lanceur changent. Les anciennes règles de génération enregistrées +dans les mondes restent intactes ; aucune promesse de migration automatique de +sauvegarde entre versions Minecraft n'est déduite de la mise à jour des fichiers. +Le canal est publié avec un artefact immuable et testé deux fois dans une +installation isolée avant les deux synchronisations de l'instance existante. + +## Résultats de la version commune + +Tous les parcours sont exécutés sur les mêmes sources beta.110, sous Java 25, +Minecraft 26.3 et Fabric API 0.160.5+26.3 : + +- Atelier d'argile : **1 min 6 s**, `STATUARY106_PASS` et import Khronos indépendant. + Fabrication payante, 16 argiles, placement, butin, Métabli et rechargement sans + le fichier source. Captures relues dans `build/integration110-evidence/`. +- Œufs : **29 s**, `EGGS107_NATIVE_PASS` ; reproduction, variantes, œufs utilisés + et distribués, générateurs et monde sans règles Sanctuary. +- Neige : **42 s**, `SNOW108_NATIVE_PASS` et `MELT109_NATIVE_PASS` ; limite de + deux blocs, saisons, protection des constructions et sauvegarde/rechargement. +- `./gradlew check build assemblePack assembleTestPack -x :sanctuary:runGameTest` : + **2 min 20 s**, **125 tâches**, réussite. + +Le JAR est comparé à l'union exacte des classes et ressources des deux livraisons +immuables beta.106 et beta.109. Les traductions et mixins réunissent tous les +éléments, sans doublon. Les sources JAR correspondent aux fichiers Java locaux. +Aucun GLB de test, classe de test, monde ou fichier Finder embarqué. + +[Pack normal](../build/Sanctuary-beta.110.mrpack) · +[Pack Test](../build/Sanctuary-Test-beta.110.mrpack). + +| Artefact | SHA-256 | +| --- | --- | +| Normal | `12a7b7279e831bf5d3489d033b875437ad9e8ba1e8cbecc6107a890d312e3029` | +| Test | `ffed5e4fcd32ca32ac96186da8e987905cd7fc4de792afe61aca9ad064895e0b` | +| JAR Sanctuary | `7cb121a6e208cdd184d80ebe61f8bc52b6703b1c1d7611eeeb700aa88d158b46` | + +Reçu : `build/integration110-artifact.json`, manifeste des sources : +`build/integration110-source-manifest.json`. Journaux : +`build/integration110-{statuary,eggs,snow,check-build}.log`. +Les essais natifs sont macOS avec serveur intégré ; pas de test Windows ni +connexion depuis un second ordinateur. La neige antérieure non suivie garde +la limite de fonte documentée dans beta.109. diff --git a/docs/collections-minecraft.md b/docs/collections-minecraft.md index 25b6295..96e7485 100644 --- a/docs/collections-minecraft.md +++ b/docs/collections-minecraft.md @@ -249,6 +249,15 @@ Identifiant proposé : `sanctuary:clay_and_bricks`. **10 recettes primaires.** **Précision :** Le vase décoré appartient à Archéologie ; la terre cuite à C11. +**Extension Sanctuary beta.102 :** seize couleurs de briques, dalles, escaliers, +argile pastel et leurs deux items, soit 192 recettes supplémentaires. Elles +sont déclarées dans `scripts/data/collections-sanctuary.json`, sans changer +le recensement vanilla ci-dessus. + +**Extension Sanctuary beta.106 :** atelier d’argile et statuaires importés. La +recette de l’atelier rejoint C06 ; la fabrication d’une miniature utilise son +menu et un bloc d’argile, avec le modèle choisi localement. + [Recettes et inventaire exacts de C06](collections-minecraft-inventaire.md#c06). [Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/clay_and_bricks.json). diff --git a/docs/colored-bricks-beta102.md b/docs/colored-bricks-beta102.md new file mode 100644 index 0000000..84840ff --- /dev/null +++ b/docs/colored-bricks-beta102.md @@ -0,0 +1,73 @@ +# beta.102 — Briques et argile de couleur + +Branche `codex/colored-bricks-beta102`, Minecraft 26.3. + +## Contrat + +Les seize PNG de briques fournis sont présents. Ils sont intégrés sans altération. +Par couleur : bloc de briques, dalle, escalier, bloc d’argile pastel, boule d’argile +et brique (items). Les 64 blocs apparaissent dans l’onglet natif des blocs colorés, +les 32 items dans les ingrédients. Les composants ont des identifiants stables +`sanctuary:_*` et des libellés français et anglais. + +La recoloration exacte par script a été choisie explicitement par le créateur. +Les silhouettes, la transparence et les nuances des textures vanilla 16 × 16 +sont conservées ; les couleurs proviennent des PNG fournis. Aucun dessin IA. + +Recettes natives : teinture de l’argile et des briques, assemblage des blocs, +cuisson de l’argile en briques, argile colorée en terre cuite vanilla de même +couleur, fabrication des dalles/escaliers et tailleur de pierre. Les recettes +rejoignent la collection existante argile/briques et les découvertes Sanctuary. +Dalles doubles, escaliers connectés, orientation, eau, outils et butin utilisent +les mécanismes natifs. Les blocs ne sont pas ajoutés à la génération du terrain. +Aucune migration de sauvegarde et aucune modification d’un monde existant. + +## Génération reproductible + +`tools/generate-colored-bricks.py --minecraft-jar ` +requiert Python avec Pillow. Les briques sources sont conservées dans les assets +et les couleurs/empreintes sont consignées dans `tools/colored-bricks-palette.json`. +Les extensions de collection sont compilées par `scripts/compile_collections.py` +depuis `scripts/data/collections-sanctuary.json` ; le recensement vanilla reste intact. + +## Validation native + +`Bricks102ClientChecks` sur Minecraft 26.3, monde plat neuf avec serveur intégré : + +- 16 familles, 64 blocs et 96 entrées d’items (dont 64 BlockItems). +- 192 recettes chargées ; fabrications, teintures, cuissons et tailleur exécutés. +- Butin des briques et escaliers ; dalle simple/double ; argile normale et Toucher de soie. +- Tags pioche/pelle et états immergés des dalles/escaliers. +- Onglets natifs : 64 blocs colorés, 32 ingrédients. +- Tous les états des 64 blocs possèdent des modèles cuits sans texture manquante. +- Découverte de l’argile : la collection ajoute les nouvelles recettes. +- Sauvegarde/réouverture du monde neuf : blocs et état de dalle haute conservés. + +Parcours final réussi en 41 s, `build/bricks102-client.log`. +[Aperçu des textures](../build/colored-bricks-beta102-preview.png) · +[Capture Minecraft](../build/bricks102-game.png). + +La vérification du rendu utilise le client natif local. Aucun test multijoueur +distant n’est revendiqué, aucune sauvegarde personnelle ni installation modifiée. + + +## Livraison + +`check build assemblePack assembleTestPack -x :sanctuary:runGameTest` réussi +en 2 min 19 s (124 tâches). Les GameTests du serveur dédié restent exclus ; +le parcours client/serveur intégré décrit ci-dessus a été exécuté séparément. +`git diff --check` et la régénération identique octet pour octet sont vérifiés. + +Les archives, sources, 192 recettes, 192 progrès de recettes, 96 libellés FR/EN, +les seize PNG fournis et les transparences vanilla ont été contrôlés. +La comparaison avec beta.101 ne trouve aucune autre classe de jeu modifiée +que l’initialisation Sanctuary et les trois nouvelles classes ColoredBricks. +Les archives beta.101 conservent leurs empreintes. +Reçu : `build/beta102-artifact.json`. + +- [Pack normal](../build/Sanctuary-beta.102.mrpack), 10 052 986 octets. + SHA-256 : `f324db41e9992df0635d870a766ec9c486167d7fc2e1ed5080f05deaf824d3c4`. +- [Pack de test](../build/Sanctuary-Test-beta.102.mrpack), 10 071 908 octets. + SHA-256 : `bf6b4a207f6b952295c01d59e788eb742d7dbee18ff5e0ad1e61ba78078348fb`. + +Livraison locale, sans publication de canal, tag ou déploiement Prism. diff --git a/docs/colored-bricks-order-beta103.md b/docs/colored-bricks-order-beta103.md new file mode 100644 index 0000000..e93b9c9 --- /dev/null +++ b/docs/colored-bricks-order-beta103.md @@ -0,0 +1,34 @@ +# beta.103 — Rangement des blocs colorés + +Branche `codex/colored-bricks-order-beta103`, Minecraft 26.3. + +Dans Blocs colorés, les ajouts sont regroupés par type : les 16 blocs de briques, +puis les 16 escaliers, les 16 dalles, les 16 blocs d’argile. Dans Ingrédients : +les 16 boules d’argile, puis les 16 briques. + +Chaque série suit l’ordre de couleurs de l’onglet natif Minecraft 26.3, +vérifié dans `CreativeModeTabs.bootstrap` : blanc, gris clair, gris, noir, +marron, rouge, orange, jaune, vert clair, vert, cyan, bleu clair, bleu, violet, +magenta, rose. L’ordre d’enregistrement des blocs/items reste stable. +Les identifiants, recettes, textures et sauvegardes sont inchangés. + +## Validation et livraison + +`check build assemblePack assembleTestPack -x :sanctuary:runGameTest` réussi +en 2 min 41 s, 124 tâches. `git diff --check` réussi. Pas de nouveau test +ni de nouvelle session graphique pour ce changement limité au rangement. + +Les sources et les deux archives ont été vérifiées. Les textures, recettes, +modèles et autres données sont identiques à beta.102, dont les archives sont +conservées. Seul le comportement de `ColoredBricks` change ; la classe interne +Stairs conserve exactement son code exécutable (seuls les numéros de lignes +de débogage changent). Reçu : `build/beta103-artifact.json`. + +- [Pack normal](../build/Sanctuary-beta.103.mrpack), SHA-256 : + `5c0b475410c7586275a59a8b14ac1770648f8196983e62c85a98dcd0fa5a1207`. +- [Pack de test](../build/Sanctuary-Test-beta.103.mrpack), SHA-256 : + `222765733711a08861e25db94ae12a4a6483bc11dbfdcaf54930786c10511d0e`. + +Les GameTests du serveur dédié restent exclus. Aucun monde personnel, +aucune installation ni aucun canal de distribution modifié. + diff --git a/docs/creative-construction-beta098.md b/docs/creative-construction-beta098.md new file mode 100644 index 0000000..c23658d --- /dev/null +++ b/docs/creative-construction-beta098.md @@ -0,0 +1,74 @@ +# beta.098 — Construire le plan en créatif + +Ticket CONSTRUCTION-098, branche `codex/creative-construction-beta098`. +À la demande du créateur, le parcours manuel du Métabli est complété par une +construction immédiate réservée au mode créatif. + +## Parcours + +Choisir une statue, une machine, un bâtiment ou un plan importé au Métabli. +Positionner et orienter l’aperçu avec K. Le bouton **Construire le plan…** +apparaît sous les coordonnées en créatif seulement. La confirmation indique +le nombre de blocs, l’origine et le fait que le plan entier sera construit, +même si une seule couche est affichée. Annuler revient aux réglages. + +Confirmer pose les blocs sans consommer de matériaux, via le protocole serveur +existant. Le plan garde son origine, son orientation et son suivi d’avancement. +Les machines reçoivent leurs composants natifs : la clé dorée reste nécessaire +pour assembler le multibloc fonctionnel. Les inventaires et entités ne sont +pas copiés depuis les fichiers de plans. + +Le bouton disparaît si le mode de jeu change. Une confirmation devenue périmée +(changement de plan, position, monde ou perte du créatif) ne lance rien. +L’envoi en cours désactive le bouton et ne peut pas être remplacé par un second. +En survie, la construction reste manuelle et le serveur refuse toujours les +envois fabriqués sans passer par l’interface. + +Les validations serveur existantes restent applicables : cases libres ou déjà +conformes, zone chargée, hauteur et bordure du monde, distance de 192 blocs, +absence d’entité à l’emplacement, permissions et interdiction des blocs +techniques sans objet. Aucun changement de génération ni format de sauvegarde. + +## Vérifications + +Suite native `Plans072ClientChecks`, Minecraft 26.3, nouveau monde plat de +développement, graine 72 : **réussie en 47 secondes**. + +- Bouton créatif visible et confirmation inspectés en capture native. +- Annulation sans placement ; statue de Creeper de 32 blocs de haut, 1 542 + cellules, construite avec vérification de chaque matériau côté serveur. +- Fourneau et abri choisis dans le catalogue physique, rotation, filtre sur + une seule couche : le bouton construit bien toutes les couches du plan. +- Perte du créatif pendant la confirmation : aucun bloc posé, bouton absent. +- Envoi falsifié en survie refusé ; pose manuelle consommant exactement un bloc. +- Régression des 88 modèles natifs, export, matériaux et interfaces FR/EN. + +Le premier passage enchaînait les plans en moins d’une seconde et rencontrait +la limite d’envoi serveur existante. Le test attend désormais 25 ticks entre +constructions ; aucun changement de cette limite n’a été nécessaire. +Le test Métabli conserve son assertion d’absence de catalogue à distance, +mais ne considère plus le nouveau bouton créatif comme interdit. + +Captures dans `build/creative098-evidence/`, journal +`build/creative098-client.log`. + +`./gradlew check build assemblePack assembleTestPack -x :sanctuary:runGameTest` +réussi en **2 min 7 s**, 124 tâches. Les GameTests sur serveur dédié restent +exclus conformément au refus antérieur d’accepter son EULA. Aucun serveur +dédié lancé ; essais uniquement sur un nouveau monde client de développement. + +Les archives ont été vérifiées : version Minecraft 26.3 et compteur beta.098, +JAR sources identiques aux fichiers de travail, aucun test embarqué. Par +rapport à beta.097, seules les classes PlanClient/PlansScreen et trois libellés +FR/EN changent dans Sanctuary. Textures, données, resource pack et archives +beta.097 conservés. Reçu : `build/beta098-artifact.json`. + +## Livraison locale + +- `build/Sanctuary-beta.098.mrpack`, 9 880 395 octets ; SHA-256 + `f06d0cc0256c6c55b1081019d0f00d2f9d29963cbf06d63b8cf1d9b303970f7a`. +- `build/Sanctuary-Test-beta.098.mrpack`, 9 899 316 octets ; SHA-256 + `f2424a39b6b96076bdff8ac2a93e74ce83d77f453aa9bfbdd18a5fa17444f573`. + +Aucun déploiement dans une instance personnelle, aucune sauvegarde personnelle +ouverte ou modifiée, aucun avancement du canal packwiz. diff --git a/docs/expeditions-beta002.md b/docs/expeditions-beta002.md index f56441b..259f286 100644 --- a/docs/expeditions-beta002.md +++ b/docs/expeditions-beta002.md @@ -29,6 +29,11 @@ et ses règles restent versionnées pour éviter de déplacer un site au recharg ## Placement des temples +**Évolution beta.097** : le refus bloquant décrit dans cette version historique +est remplacé par une recherche de secours, puis un placement garanti des pièces +vanilla avec fondation si nécessaire. Les sites historiques admissibles restent +prioritaires. Voir le [contrat du correctif](temple-crash-beta097.md). + Les candidats sont des chunks de l’intérieur de chaque île, à au moins 48 blocs de son centre, ordonnés par un tirage déterministe. La recherche filtre le type voulu, puis conserve les conditions natives de biome, de pente et de fondation. diff --git a/docs/exploration-familiers-beta100.md b/docs/exploration-familiers-beta100.md new file mode 100644 index 0000000..91a3ad9 --- /dev/null +++ b/docs/exploration-familiers-beta100.md @@ -0,0 +1,85 @@ +# beta.100 — Statues découvertes et promenade des familiers + +Branche `codex/exploration-familiers-beta100`. Minecraft 26.3. + +## Contrat + +Le catalogue des statues et son sélecteur ne proposent que les espèces +observées, tuées ou ayant tué le joueur. Le Métabli envoie une liste calculée +par le serveur à partir des notes `seen_mobs` et des statistiques natives. +Le champ d’identifiant et la capture de cible respectent aussi cette liste. +Les plans déjà construits/importés restent des plans de blocs ordinaires. +Aucun nouveau format de sauvegarde, aucune migration ni changement de terrain. + +Les familiers au repos choisissent une destination atteignable puis font une +pause. Le tempérament existant règle leur rayon, leur allure et leurs pauses : +curieux explorateur, joueur plus vif, calme plus posé. Les protecteurs restent +près du point gardé. Les déplacements d’attaque, la fuite, les ordres, le portage +et les montures gardent la priorité. Les volants ne suivent plus une orbite +perpétuellement recalculée. Les chemins ratés sont abandonnés et les recherches +restent bornées dans les chunks chargés. Les identités et caractères existants +ne sont pas retirés au sort. + +## Validation + +`Exploration100ClientChecks` : **réussi en 4 min 37 s**. Nouveau monde plat +avec serveur intégré, sans serveur dédié : + +- Une vache réellement visée débloque sa statue. Les statistiques natives + tué/par-qui-tué ajoutent zombie et squelette. Un œuf de cochon porté ne suffit + pas. Le client reçoit la liste du serveur ; une génération de cochon reste + refusée même en créatif. Capture du catalogue inspectée. +- Douze promenades réelles : loup, poule, slime, chauve-souris, chacun calme, + curieux et joueur. Plusieurs cases traversées, pauses, maintien près du joueur. + Le tempérament calme est testé avec personnalité protectrice, les deux autres + avec pacifiste pour isoler le déplacement au repos. +- Régression `Autonomy074ClientChecks` complète : quatre personnalités, + déplacements et dégâts réels, garde, cibles neutres, permissions, obstacles, + mode travail, ordres H bref/maintenu et touche reconfigurée. +- `.schem` Sponge v2/v3 : renommage natif ancien `grass` vers `short_grass`, + conservation exacte du fichier source et refus explicite d’un bloc de mod absent. + +Journal : `build/exploration100-client.log`. Captures : +`build/exploration100-evidence/`. La matrice ne constitue pas un nouveau playtest +visuel de toutes les espèces, ni une validation à plusieurs clients distants. +`Schematics100ClientChecks` : **réussi en 1 min 2 s**. Le client refuse un +fichier incompatible dans le Métabli sans fermer le menu, affiche son identifiant +fautif, puis importe un `.schem` valide. Fermeture, réouverture de la même +sauvegarde et ouverture d’un deuxième monde passent, avec conservation de la +structure et remise à zéro du plan/de la session d’atelier. Le fichier rejeté +reste identique. Journal : `build/schematics100-client.log`. + +Ces essais macOS ne reproduisent pas le crash Windows rapporté ; ils ne +constituent pas une correction démontrée de cet incident. `check build assemblePack assembleTestPack` réussit en **2 min 17 s**, +124 tâches, avec `-x :sanctuary:runGameTest` (serveur dédié exclu conformément +au refus de son EULA). Les deux packs sont vérifiés : même JAR testé, sources +correspondantes, libellés FR/EN, textures et archives beta.099 conservées. +Reçu : `build/beta100-artifact.json`. Aucun déploiement personnel ni publication +du canal packwiz. + +## Import `.schem` et incident Windows signalé + +Les palettes Sponge v2/v3 utilisent maintenant le convertisseur natif +`References.BLOCK_STATE` quand leur `DataVersion` est connue et antérieure. +Seule une copie de palette en mémoire est convertie ; le fichier importé et +les sauvegardes ne sont jamais réécrits par cette opération. Une version +future, un bloc de mod absent ou une propriété réellement incompatible restent +refusés. Sans version source, le parseur reste strict et ne devine pas une +conversion. Le message d’import indique le bloc/état fautif. + +Le signalement Windows décrit un échec d’ouverture, un monde absent de la liste +puis une fermeture du client lors du choix d’un autre monde. Aucun journal ni +fichier précis n’est disponible. L’inspection ne trouve aucun accès de suppression +des mondes dans l’importeur ; les plans actifs sont effacés en mémoire lors des +changements de connexion. Cela ne prouve pas la cause du signalement : sa +reproduction et le diagnostic Windows restent ouverts. Les fichiers utiles sont +`logs/latest.log` et, s’il existe, le rapport daté de `crash-reports/` dans le +dossier Minecraft de l’instance concernée. Ne pas supprimer ni convertir les +sauvegardes pour tenter de résoudre cet incident. + +## Archives locales + +- [Sanctuary-beta.100.mrpack](../build/Sanctuary-beta.100.mrpack), 9914561 octets. + SHA-256 : `7e24ba5e77c7fbb88cfadb55b3e373ad7e998381fd1c56e431aa79b4619a8075`. +- [Sanctuary-Test-beta.100.mrpack](../build/Sanctuary-Test-beta.100.mrpack), 9933483 octets. + SHA-256 : `a43bf61dc38c3c91f4cb09022040099ee1ca9274a6b00751178c80ee204dd944`. diff --git a/docs/flying-landing-beta101.md b/docs/flying-landing-beta101.md new file mode 100644 index 0000000..26e09e7 --- /dev/null +++ b/docs/flying-landing-beta101.md @@ -0,0 +1,58 @@ +# beta.101 — Atterrissage des familiers volants + +Branche `codex/flying-landing-beta101`, Minecraft 26.3. + +Le profil de vol du familier doit désactiver l’accumulation et le traitement des +chutes de son entité commune. Une descente pilotée, autonome ou un contact avec +le sol ne doit causer ni dégâts, ni animation/son de coup, ni entrée en combat. +Les vraies attaques et les chutes des familiers terrestres gardent leurs règles. +Aucun changement de navigation, de sauvegarde, de texture ou de terrain. + +## Cause et correction + +L’entité Sanctuary commune hérite de `PathfinderMob`. Son contrôle de vol et +l’absence de gravité ne remplacent pas les méthodes natives de chute. Ces +méthodes pouvaient appeler `FamiliarBattle.hurt`, produire son animation/son de +coup, interrompre une technique et enregistrer du combat. + +`checkFallDamage` remet maintenant la distance à zéro pour les profils volants. +`causeFallDamage` refuse aussi un appel avec une distance déjà calculée, avant +les sons et les dégâts natifs. Les autres profils délèguent toujours au code +Minecraft. Les sources de dégâts ordinaires ne sont pas filtrées. + +## Validation + +Reproduction native sur beta.100 : le perroquet accepte le callback de chute, +et l’assertion « Flying familiar rejects native fall callback » échoue. +Journal conservé : `build/landing101-before.log`. + +`Landing101ClientChecks` réussit après correction en **1 min 5 s**, dans un +nouveau monde plat de développement avec le serveur intégré : + +- Les douze espèces du registre ayant un profil volant : perroquet, + chauve-souris, abeille, allay, Wither, breeze, phantom, vex, blaze, ghast, + ghast joyeux et dragon. +- Callback natif de chute à sept blocs, descente à 0,25 bloc par tick jusqu’au + contact réel avec le sol, puis mouvement rapide vers le sol. +- Santé inchangée, distance de chute nulle et absence de mise à jour de + `lastHurt`, donc pas de passage dans la branche qui diffuse le faux coup. +- Une attaque de zombie reste acceptée et enlève de la santé pour chaque espèce. +- Un loup terrestre conserve les dégâts de sa chute. + +Les descentes sont pilotées par le test via les collisions natives ; ce n’est +pas un nouveau playtest de tous les trajets autonomes ou un essai multiclient. +Journal : `build/landing101-client.log`. Aucun monde personnel n’est utilisé. +`check build assemblePack assembleTestPack` réussit en **2 min 22 s**, +124 tâches, avec le serveur dédié exclu (`-x :sanctuary:runGameTest`). +Les archives normal/Test embarquent le même JAR vérifié et ses sources exactes. +Seul `FamiliarEntity` et les numéros de ligne de deux classes internes changent +par rapport à beta.100. Les ressources, textures et archives beta.100 sont +conservées. Reçu : `build/beta101-artifact.json`. Aucun déploiement personnel +ni publication du canal packwiz. + +## Archives + +- [Sanctuary-beta.101.mrpack](../build/Sanctuary-beta.101.mrpack), 9914792 octets. + SHA-256 : `3008127d2bb09556c4997c00350e82ff26af5402b69bc7c09c822b24ff4bfe74`. +- [Sanctuary-Test-beta.101.mrpack](../build/Sanctuary-Test-beta.101.mrpack), 9933717 octets. + SHA-256 : `854a8b6a42ac7ca0236e0c19b9000808535df273911b9c6a72844097a49358a0`. diff --git a/docs/fourneau-allume-beta094.md b/docs/fourneau-allume-beta094.md new file mode 100644 index 0000000..3c45ae3 --- /dev/null +++ b/docs/fourneau-allume-beta094.md @@ -0,0 +1,79 @@ +# beta.094 — Foyers allumés du Fourneau + +La chaleur partagée allume toute la façade du Fourneau, y compris si la cuisson +se déroule dans une case intérieure. Les deux ouvertures reçoivent des braises +orange et jaunes. La pierre et les trois textures originales sont conservées. +Quand la chaleur est épuisée, la façade reprend son aspect éteint. + +Les 27 sources de particules des fours composants sont remplacées par une seule +animation de façade : une flamme et une fumée dans chacun des deux foyers. +Leurs positions correspondent aux pixels des ouvertures, à 0,02 bloc devant la +face, dans les quatre orientations. Un four isolé garde son animation native. +La dissociation rétablit l'état lumineux individuel des fours. + +## Assets et rendu + +- Original inchangé : `assets/sanctuary/textures/block/fourneau/front.png`. +- Variante de feu : `assets/sanctuary/textures/block/fourneau/front_on.png`. +- `tools/fourneau-embers.json` délimite les bandes des ouvertures à illuminer. +- `python3 tools/generate-fourneau-models.py` régénère les modèles éteints/allumés. + +La variante a été créée avec l'outil intégré **imagegen**, puis réduite en +48 × 48 par échantillonnage au plus proche pour la densité native du modèle. +La génération ayant aussi redessiné la pierre, le rendu prélève uniquement les +pixels de feu dans les ouvertures ; tout le reste utilise l'image originale. +Les fines faces de braises sont décalées de 0,002 pixel pour éviter le z-fighting. + +Prompt retenu : « Edit the provided 48 by 48 pixel Minecraft furnace facade +texture. Create its LIT variant. Preserve the pixel-art grid, grey stone, mortar, +arches, outer edges and dimensions. Change only the black/dark interiors of the +two arched openings to Minecraft-style orange, red and yellow embers/fire pixels, +bright yellow near the bottom, orange above. Flames stay inside the holes. +No perspective, border, labels or new elements. » + +Le choix du modèle utilise l'état `LIT` natif. Les snapshots d'assemblage, leurs +paquets réseau et le schéma de sauvegarde restent identiques. Aucun paquet de +particules n'est envoyé par le serveur. Aucun monde personnel n'est modifié. +Le pack de textures intégré reste à son édition beta.090 ; ces assets sont ceux +du mod beta.094. + +## Vérifications + +Le test client natif avec serveur intégré est passé sur un nouveau monde plat de +développement, graine 85 (`build/fourneau094-client.log`, 1 min 5 s) : + +- 27 composants synchronisés à cheval sur quatre chunks, quatre orientations. +- Chauffe réelle depuis une case intérieure, façade entière allumée puis éteinte. +- 800 émissions interceptées à la sortie de l'animation native : coordonnées + devant la façade, sur un pixel de feu situé dans une ouverture sombre originale. +- Aucune nouvelle particule à froid ; particules natives d'un four isolé conservées. +- Rechargement des ressources, départ/retour dans les chunks, dissociation, + reconstruction et destruction ; conservation du contenu d'origine. +- Captures allumé/éteint inspectées dans `build/fourneau094-evidence/`. + +Commande du test : + +```sh +./gradlew :sanctuary:runClientGameTest -x :sanctuary:runGameTest \ + -PsanctuaryClientTests=true -PsanctuaryFourneau085ClientTests=true \ + -PsanctuaryQuickTests=true -PsanctuaryClientNoVsync=true +``` + +Les particules déjà émises finissent naturellement leur durée de vie après +l'extinction, comme celles des fours Minecraft. Test natif macOS ; pas de test +avec shaders tiers ni de session LAN à deux clients pour cette livraison. +Le serveur dédié de GameTest est exclu, conformément au refus antérieur de son +EULA. Aucun déploiement personnel ou changement de sauvegarde. + +`check build assemblePack assembleTestPack -x :sanctuary:runGameTest` : réussi +en 2 min 31 s, 124 tâches (`build/fourneau094-check.log`). La régénération des +216 modèles est reproductible. `git diff --check` passe. + +Les archives normal/Test ont été exportées et vérifiées : Minecraft 26.3, +version beta.094, même JAR Sanctuary intégré aux deux packs, sondes de test +absentes. Les assets existants, JEI et les archives beta.093 sont inchangés. +Le reçu `build/fourneau094-artifact.json` conserve les empreintes SHA-256 ; +`build/verify-fourneau094.py` reproduit ces contrôles. + +- [Pack normal](../build/Sanctuary-beta.094.mrpack). +- [Pack de test plat](../build/Sanctuary-Test-beta.094.mrpack). diff --git a/docs/living-hats-seasons-beta105.md b/docs/living-hats-seasons-beta105.md new file mode 100644 index 0000000..d75cd8b --- /dev/null +++ b/docs/living-hats-seasons-beta105.md @@ -0,0 +1,143 @@ +# beta.105 — Météo saisonnière, captures et chapeaux vivants + +Branche `codex/living-hats-seasons-beta105`, Minecraft 26.3. + +## Contrat avant modification + +- Molette dans le fût : navigation entre pages, bornée, sans perdre les objets + du curseur ni déplacer la souris au centre. +- Panoramas : couleurs et brume du shader Sanctuary actif, six faces cohérentes, + profondeur conservée et caméra restaurée même après erreur. +- Real Time : probabilités liées aux saisons astronomiques du calendrier réel. + Été surtout ensoleillé sans profil ciel gris ; automne plus humide ; hiver + avec neige. Des journées claires restent possibles dans les quatre saisons. + Le créateur demande explicitement flocons et accumulation naturelle au sol. + Le tirage du jour déjà sauvegardé reste stable ; nouveaux tirages saisonniers + les jours suivants, commandes opérateur disponibles. Aucune Weather TNT. +- Mobs équipés : coups et boules de neige activent l’objet ; briquet, feu + d’artifice propulsant le porteur, spawner natif configuré, paratonnerre attirant + les éclairs. Les consommables et l’usure restent réels. +- Cultures sur tête : croissance et récolte des produits/graines, sans duplication + de la graine initiale au retrait. Les aliments attirent les espèces qui les + mangent ; une carotte sur bâton guide les cochons vers le porteur. +- Livre et plume : journal intime écrit en réaction aux caresses et blessures, + voix différente selon les quatre personnalités ; souvenirs variés, pas de log + technique. Pages existantes conservées et limites natives respectées. + +## Contrat de données + +Aucune sauvegarde personnelle n’est modifiée pendant les essais. Les objets et +leur état continuent d’utiliser les données additives existantes de chapeau. +Les journaux restent de véritables livres Minecraft récupérables. L’éventuel +nouveau profil neige étend l’énumération météo : anciens états lisibles, aucune +conversion automatique du jour courant. Un retour à une ancienne version après +une journée neige nécessite d’abord de sélectionner un profil météo ancien. +La neige peut modifier les blocs exposés pendant le jeu, selon la demande du +créateur ; pas de régénération, d’expansion ni de changement de terrain au chargement. + +## Utilisation + +- Dans le fût, molette vers le bas : page suivante ; vers le haut : précédente. + Le défilement concerne le coffre et sa colonne de contrôles. Un objet tenu au + curseur bloque le changement de page, comme les boutons existants. +- `/sanctuary weather forecast` annonce les sept prochains jours. + `/sanctuary weather snow` et `snow_showers` sélectionnent respectivement neige + continue et averses ; leurs équivalents `/weather` sont également disponibles. + Le mode Vanilla conserve le cycle Minecraft. +- Sur un mob équipé, clic droit à main vide : caresse du livre, activation du + briquet/de la fusée, récolte d’une culture mûre. Coup ou boule de neige : + activation. Maj + clic droit à main vide : récupération de l’objet. +- Blé, carottes, pommes de terre, betteraves : croissance des cultures portées, + récolte native et replantation. Les produits mûrs sont aussi rendus si l’on + retire la graine. Les plantes décoratives et tiges de melon/citrouille ne + deviennent pas des cultures de fruit mobiles. +- Le spawner conserve l’espèce configurée et les contraintes Minecraft : + proximité d’un joueur, espace, lumière et limite d’entités. Une activation + lance une tentative immédiate ; elle ne garantit pas une apparition si + ces conditions ne sont pas réunies. +- Le paratonnerre attire les éclairs naturels sous un ciel dégagé à portée + native, puis émet son impulsion redstone. Le briquet respecte la règle de modification du terrain par les mobs. + La fusée consomme l’objet, utilise ses effets natifs et propulse le porteur. +- Les aliments portés attirent les animaux qui les consomment dans un rayon + de 10 blocs, avec abandon à 12 blocs ou sans visibilité. La carotte sur bâton + guide ainsi les cochons. Priorités de fuite et de reproduction conservées. +- Le journal reprend la personnalité du familier ; celle d’un animal ordinaire + est déterminée de façon stable par son identité et son espèce. FR/EN selon + la langue du premier lecteur-interacteur. Les pages précédentes sont + conservées (limite native 100 pages), mais ne sont pas diffusées dans le + paquet d’apparence du chapeau. + +## Calendrier et périmètre + +Longitude solaire apparente calculée localement à partir des formules publiques +[NOAA Solar Calculator](https://gml.noaa.gov/grad/solcalc/), avec contrôle des +quatre passages 2026 contre les horaires de l’[US Naval Observatory](https://aa.usno.navy.mil/data/Earth_Seasons). +Le tirage est journalier : le jour civil d’un équinoxe/solstice adopte la nouvelle +saison, selon le fuseau Real Time. L’hémisphère sud inverse les saisons via la +latitude solaire configurée. Il ne s’agit pas de la météo réelle d’une ville. +Les pondérations de `weather.json` restent la base, modulée par la saison. +Sur 10 000 graines par saison : journées claires 64,41 % / 94,79 % / 40,25 % / +49,05 % (printemps/été/automne/hiver) avec la configuration par défaut. + +La neige passe par les précipitations natives des chunks chargés, respecte +la limite native d’accumulation, la lumière et les supports Minecraft. Les biomes +sans précipitations restent secs. Les chaudrons reçoivent aussi de la neige. +La neige déposée reste un bloc natif : pas de balayage saisonnier supprimant +les blocs des constructions. Sa fonte suit les règles Minecraft. +Les captures intègrent le post-traitement et la brume Sanctuary ; aucune +compatibilité avec un moteur externe Iris/Canvas/Oculus n’est déclarée. + +## Vérifications + +- `seasonal105Smoke` : 40 000 tirages, stabilité par graine/jour, + équinoxes/solstices 2026 à ± une heure, inversion sud, proportions saisonnières, + persistance d’une journée de neige et passage au lendemain. +- `Living105ClientChecks` : client natif Minecraft 26.3 avec serveur intégré, + nouveau monde plat jetable. Journal avec page préalable, caresse/blessure, + récupération, récolte/replantation/retrait, vache attirée par le blé, cochon + guidé, vraie méthode d’impact de boule de neige, feu/usure, vol d’une vache, + apparition du spawner configuré, éclair/impulsion/extinction du paratonnerre, + neige déposée par `tickPrecipitation`, requête de flocons côté client, + molette suivant/précédent/bornes et deux panoramas complets de six faces. +- Pour chaque face à saturation 0, contrôle des pixels désaturés ; comparaison + à saturation 200, présence de `sanctuary:vanilla_light` et profondeur du sol + conservée. Les vues sont soumises séparément au GPU pour éviter de réutiliser + les tampons natifs avant leur soumission. Aucun échec de post-traitement dans + l’essai final (53 secondes). + +Commande de reproduction : + +```sh +JAVA_HOME=/chemin/vers/jdk25 ./gradlew :sanctuary:runClientGameTest \ + -PsanctuaryClientTests=true -PsanctuaryLiving105ClientTests=true \ + -PsanctuaryClientNoVsync=true -PsanctuaryQuickTests=true +``` + +Le délai serveur de trois ticks entre changements de page est supprimé : les +paquets sont toujours validés contre l’identifiant du menu ouvert et son curseur. +Les anciennes pages ne peuvent donc pas accepter de clics après remplacement. +Le client et le serveur doivent utiliser ensemble beta.105 pour les nouveaux +profils réseau de neige. Les anciens états enregistrés restent lisibles. + +## Livraison locale + +`check build assemblePack assembleTestPack -x :sanctuary:runGameTest` réussi +sur la copie isolée `build/release-beta105` : 2 min 43 s, 125 tâches. +Le test dédié est exclu conformément au refus antérieur de son EULA ; le test +natif de cette livraison utilise un serveur intégré dans un monde neuf jetable. + +La copie part des sources et ressources vérifiées beta.104, auxquelles sont +appliqués les 25 fichiers Java concernés, les libellés météo FR/EN et les mixins. +Elle évite d’embarquer les modifications du chantier Statuaire beta.106 arrivé +en parallèle dans le dossier partagé. Le travail beta.106 est conservé. +`build/beta105-source-manifest.json` décrit les sources de cette compilation. + +Les sources JAR correspondent à cette copie ; les ressources du pack beta.104 +restent identiques sauf les deux fichiers de langue. JAR sans classes de test, +archives et métadonnées 26.3 vérifiées ; archives beta.104 inchangées. Rapport : +`build/beta105-artifact.json`. Aucun déploiement ni publication du canal. + +- [Sanctuary-beta.105.mrpack](../build/Sanctuary-beta.105.mrpack), 10083681 octets. + SHA-256 : `396c31ad80b4826e5cc0530001e90e498dfb3d36f951cb46d01b32870bf3212a`. +- [Sanctuary-Test-beta.105.mrpack](../build/Sanctuary-Test-beta.105.mrpack), 10102603 octets. + SHA-256 : `6398eb413ea45cf383b71d5be07cb1eef6995158fe0909b06deabb36c1623ce4`. diff --git a/docs/metabli-beta096.md b/docs/metabli-beta096.md new file mode 100644 index 0000000..dc5d0b6 --- /dev/null +++ b/docs/metabli-beta096.md @@ -0,0 +1,104 @@ +# beta.096 — Métabli et commandes de construction + +Le parcours créatif est complété depuis [beta.098](creative-construction-beta098.md) +par un bouton de construction immédiate. Le présent document décrit beta.096. + +## Contrat de monde avant implémentation + +Neuf établis à plat, en carré 3 × 3, sont assemblés volontairement à la clé +avec la confirmation habituelle. Aucun établi existant n'est converti au +chargement. Les neuf composants deviennent `sanctuary:metatable`, propriété +`part` de 0 à 8 (x + 3z). Leur origine est déduite de cette propriété. +Ces états sont stockés normalement dans les chunks ; aucun SavedData ancien +n'est modifié, aucun inventaire ou contenu n'est copié. Pas de BlockEntity. +Le contrôle des neuf cases est borné et n'impose pas de chargement de chunk. + +Une dissociation restaure neuf établis. Casser un composant dépose son établi +selon le butin natif et restaure les autres sans drops supplémentaires. +Les composants déchargés sont contrôlés à leur reprise. Une machine partielle +est inaccessible jusqu'à validation/restauration. Les pistons ne déplacent pas +un Métabli assemblé. Les permissions serveur s'appliquent à chaque composant. + +Les deux PNG fournis sont conservés octet pour octet : dessus 48 × 48, +côtés 48 × 16, prélevés par tiers sans étirement. Le dessous utilise les +planches de chêne du pack actif. + +## Parcours retenu + +Clic droit au Métabli → catalogue → choix unique du projet → retour dans +le monde. K ouvre les commandes du projet : ancrer au point visé, décaler, +tourner, afficher une couche, masquer/afficher et consulter les besoins. +Changer de projet, créer une autre statue, importer ou abandonner le plan +se fait au Métabli. Esc ferme seulement l'interface et ne retire pas le plan. +Un import ou une génération en cours n'est accepté que si le joueur est encore +à l'atelier qui a initié le choix. Le choix ne pose aucun bloc. + +Construction manuelle avec l'inventaire natif. Aucun collage ni chantier +automatique dans ce parcours, même en créatif. Les fonctions serveur de collage +créatif historiques restent contrôlées par leurs règles existantes. +Le projet et son aperçu restent locaux au joueur, comme dans beta.072 ; ils +sont effacés au changement de monde/dimension ou à la déconnexion. L'export +reste disponible pour les conserver. Le chantier partagé persistant, les +catalogues en datapack serveur et les services commerciaux sont des lots futurs. +Aucune économie n'est ajoutée. + +## Catalogue initial et extension + +- Statues : les modèles natifs déjà pris en charge, avec hauteur et palette. +- Machines : Fourneau, Fût, super piston normal et gluant. Le plan guide les + blocs sources ; la clé réalise ensuite leur assemblage fonctionnel. +- Bâtiments : abri de 5 × 5 et passerelle de 3 × 7, deux plans simples de départ. +- Mes plans : bibliothèque locale et imports existants `.litematic`, `.schem` + et `.nbt`. Les anciens `.schematic` demandent une conversion externe. + +`ConstructionCatalog.ENTRIES` est le point d’extension des plans intégrés : +identifiant, catégorie et fabrique de `BuildPlan`, avec nom et aide FR/EN. +Une machine fonctionnelle conserve son implémentation serveur propre : un plan +ne lui confère ni inventaire prérempli ni commandes ni service commercial. +Le catalogue configurable par datapack reste à implémenter. + +## Utilisation + +Poser neuf établis en carré à plat. Deux clics à la clé dorée confirment le +Métabli ; clic droit pour l’ouvrir. Choisir un plan ferme le catalogue. +K ouvre les commandes de construction, avec aperçu, décalage d’un bloc, +rotation par quart de tour, couches et matériaux restants. Poser les blocs +normalement ; revenir au Métabli pour changer de choix ou abandonner. +Maj + clic droit à la clé dissocie l’atelier. + +## Vérifications + +Deux suites natives sur Minecraft 26.3, en nouveaux mondes de développement : + +- `Metatable096ClientChecks`, graine 96 : assemblage en deux clics sur quatre + chunks, ouverture par clic droit natif, quatre rubriques, sélection unique, + K réservé aux réglages, refus de changement/import à distance, rotation et + translation, pose native payée, annulation d’une génération en quittant + l’atelier, statue puis export/import, interfaces FR/EN, casse pendant le menu, + un seul drop et huit établis restaurés, réassemblage, déchargement réel des + chunks puis retour, dissociation des neuf cases. Réussi en 1 min 11 s. +- Régression `Plans072ClientChecks` adaptée au Métabli : capture des **88 modèles** + sans refus, statue de 32 blocs, rotations, matériaux, export, ancien protocole + de collage créatif et refus en survie, pose native consommant son bloc, + interfaces FR/EN. Réussi en 1 min 7 s. + +Captures inspectées dans `build/metabli096-evidence/`. Le premier passage du +nouveau test attendait le déchargement via `ClientLevel.hasChunkAt`, qui ne +reflète pas le cache client ; le test utilise désormais `getChunkNow`, comme +les autres essais natifs. Le parcours fonctionnel précédant cette attente +avait déjà réussi. Ce premier journal est conservé séparément. + +`./gradlew check build assemblePack assembleTestPack -x :sanctuary:runGameTest` +réussi : 124 tâches, 2 min 20 s. Voir `build/metabli096-check.log` et le reçu +`build/metabli096-artifact.json`. Textures d’origine comparées octet pour octet, +modèles découpés par tiers, JAR sources et archives vérifiés ; archives beta.095 +conservées. Les tests dédiés sont exclus conformément au refus antérieur d’EULA. +Aucun serveur dédié démarré, aucune EULA acceptée, aucun déploiement personnel. + +## Livraison locale + +Packs normal et Test `build/Sanctuary-beta.096.mrpack` et +`build/Sanctuary-Test-beta.096.mrpack`, Minecraft 26.3 finale. +Le canal packwiz et l’instance Prism ne sont pas modifiés. Les dépendances et +le pack de textures intégré beta.090 restent identiques ; les textures du +Métabli sont les ressources propres du mod. diff --git a/docs/metabli-construction.md b/docs/metabli-construction.md new file mode 100644 index 0000000..4a10b93 --- /dev/null +++ b/docs/metabli-construction.md @@ -0,0 +1,185 @@ +# Métabli — atelier de construction et catalogue extensible + +Statut : **conception de référence**, le 16 septembre 2026. +Le catalogue, les commandes K et l’exemple généré évoluent dans +[beta.099](catalogue-progression-beta099.md). +Le premier lot est livré dans [beta.096](metabli-beta096.md), branche +`codex/metabli-beta096` : multibloc, catalogue et commandes de construction. +Les chantiers partagés persistants, datapacks de catalogue et services décrits +ci-dessous restent des propositions pour les lots suivants. Le contrat beta.096 +précise le fonctionnement effectivement implémenté. + +## Direction demandée + +Un multibloc posé dans le monde ouvre le menu de construction. Aucun bouton +supplémentaire dans le menu pause. Le système réunit statues, machines et +bâtiments, et doit pouvoir accueillir de nouveaux contenus et usages. +« Métabli » reprend ici le nom du nouveau message ; le catalogue historique +employait « Métablit ». Aucun identifiant publié n'est renommé. + +## Ce que le jeu possède déjà + +- `PlansScreen` propose Statue, Bibliothèque et Construction via K. +- `StatueVoxelizer` et la capture cliente produisent des statues à partir des + modèles et textures du jeu ; `BuildPlan` porte les blocs et leurs états. +- `PlanClient` sait ancrer un aperçu, le tourner, isoler une couche et compter + les blocs corrects, restants et en conflit. Cet état est actuellement local. +- `SchematicIO` lit les formats pris en charge dans la bibliothèque ; l'import + ne reprend pas les inventaires, scripts ou entités des fichiers tiers. +- `PlanService` autorise le collage uniquement en créatif. En survie, le joueur + pose aujourd'hui ses blocs lui-même : aucun moteur de chantier automatique. +- La clé dorée et les machines existantes donnent un précédent pour l'assemblage + volontaire, la dissociation et l'autorité serveur. + +Références : [plans existants](statues-plans-beta072.md), +[multiblocs existants](multiblocs-beta084.md), +[catalogue historique](multiblocs-conception.md). + +## Le Métabli dans le monde + +Proposition de forme : **neuf établis à plat, en 3 × 3 sur un bloc de hauteur**, +assemblés volontairement avec la clé dorée. Forme et apparence à confirmer +avant le code ; ne pas imposer les 27 composants du Fourneau à cette machine. +Un clic droit sur un composant assemblé ouvre le même atelier. + +Le Métabli sert à choisir, préparer et gérer un projet. La construction est +ancrée à un autre emplacement choisi dans le monde ; elle n'a pas à occuper +la place de l'atelier. L'aperçu indique clairement sa base et son orientation. +Proposition initiale : un chantier actif par Métabli, plusieurs participants. + +K reste disponible pour l'aperçu et les réglages du plan en cours. Le premier +lot ne supprime pas les outils locaux déjà livrés ; le nouveau catalogue et +la gestion du chantier partagé sont accessibles depuis le Métabli. + +## Un menu commun + +Interface native et sobre : catégories en haut, liste et recherche à gauche, +aperçu et fiche du projet à droite, actions en bas. Les catégories n'ouvrent +pas quatre nouveaux menus indépendants. + +| Catégorie | Contenu | Réglages utiles | +| --- | --- | --- | +| Statues | Générateur actuel de mobs ; autres modèles ultérieurement | Modèle, taille, palette | +| Machines | Fourneau, Fût, super pistons ; plans de circuits redstone validés | Orientation, variante, entrées et sorties | +| Bâtiments | Maisons, ateliers, ponts et autres plans ajoutés au catalogue | Dimensions, variantes de matériaux compatibles | +| Mes plans | Bibliothèque locale et imports actuels | Nom, dimensions, matériaux, rotation | + +Une fiche affiche la fonction réelle, l'encombrement, les matériaux requis +et manquants, ainsi que la condition de déblocage éventuelle. Les exemples de +bâtiments et de circuits ci-dessus sont des contenus à créer, pas un catalogue +déjà présent. Une fonction absente du jeu n'est pas présentée comme disponible. + +Parcours proposé : choisir → configurer → positionner l'aperçu → confirmer +l'emplacement → construire → utiliser. Une fiche de chantier conserve +l'avancement et les besoins en matériaux. Le mode retenu par le créateur est la **construction manuelle guidée** : +les joueurs apportent leurs matériaux et posent eux-mêmes les blocs. + +## Mode retenu : construction manuelle guidée + +Décision confirmée par le créateur le 16 septembre 2026. Le Métabli ne place +pas les blocs et ne prélève pas les objets. La pose, les outils, les matériaux +et les règles de voisinage restent ceux du jeu. Aucun moteur automatique, +réserve de chantier, délai de fabrication ou consommation différée à créer. + +Réutiliser l'aperçu existant : les blocs terminés s'effacent de la projection, +les conflits sont signalés et les couches permettent de suivre un grand plan. +Le chantier commun pourra partager l'ancrage et la progression ; chaque joueur +contribue avec son propre inventaire. Le contrôle de droits sur la construction +elle-même reste celui du monde, indépendamment des droits d'édition du plan. + +La fiche distingue **blocs à placer** et **objets nécessaires**. Le compteur +actuel travaille sur les états de blocs ; annoncer une liste exacte d'objets +exige d'adapter les portes et lits (un objet, deux cases), les dalles doubles +(deux objets) et les autres cas particuliers. Aucun de ces calculs ne peut +modifier ou doubler la consommation native du joueur. + +## Une construction a un plan et, parfois, une fonction + +Le **plan** décrit la forme : blocs, états, variantes et points de raccordement. +La **fonction** décrit un comportement éventuellement ajouté par Sanctuary. + +- Une statue ou une maison ordinaire a seulement besoin de son plan. +- Un circuit redstone fonctionne grâce aux blocs réellement construits. +- Un Fourneau doit en plus être validé et assemblé par le système existant. +- Une boutique aura besoin d'un composant commercial : propriétaire, stock, + offres et échanges. Construire son bâtiment ne crée pas ce service à lui seul. + +Pour les machines reconnues, le plan complet et les permissions sont vérifiés +avant l'assemblage final ; un simple import de blocs ne peut pas s'attribuer une +fonction spéciale. Les ports indiqués dans le plan tournent avec la structure +(entrée d'objets, sortie, commande redstone, face d'interaction). + +La demande de boutique donne un point d'extension, **pas une livraison de +l'économie maintenant**. On pourra préparer un local de boutique et lui +rattacher le service lorsque le commerce sera défini et implémenté. Son futur +signal redstone pourra par exemple indiquer un stock disponible ou une vente, +sans confondre le signal avec l'opération d'échange elle-même. + +## Étendre le catalogue sans refaire l'interface + +Proposition : des définitions de catalogue en datapack serveur, avec des +identifiants stables et une version de définition. Une entrée comporte : + +- nom et description FR/EN, catégorie et tags ; +- source du plan ou générateur connu, paramètres et variantes autorisées ; +- condition de déblocage vérifiable côté serveur ; +- matériaux et opérations de placement, calculés depuis la variante retenue ; +- ports de raccordement et type de fonction facultatif. + +Ajouter un bâtiment ou un circuit utilisant les mécanismes existants revient +à ajouter un plan et sa fiche. Une nouvelle mécanique, comme le commerce ou +une machine inédite, demande un module de code serveur enregistré, puis une +fiche qui l'utilise. Un datapack ne devient pas un interpréteur de commandes +arbitraires et un fichier importé ne fournit pas son propre code exécutable. + +Les générateurs de statues et les fichiers locaux deviennent deux fournisseurs +de plans parmi d'autres. Les catégories servent à naviguer ; elles ne dictent +pas le moteur de placement. Découvertes, advancements ou aptitudes pourront +débloquer des entrées, sans supposer que ces règles existent déjà pour les plans. + +## Persistance et reprise à définir avant le code + +Le registre actuel des multiblocs décrit un cube de 27 cases avec un booléen +Fourneau/Fût. Il n'est pas extensible à un Métabli par ajout d'une troisième +valeur. Prévoir un registre dédié pour cette nouvelle machine et ses chantiers, +sans modifier le schéma existant ; un contrôle commun empêchera de partager +un composant avec les anciennes machines. + +Le futur contrat versionné devra fixer l'identité du Métabli et du projet, +la dimension, l'ancrage, l'orientation, les droits d'édition et la reprise +du suivi. Les matériaux restent dans les inventaires Minecraft ; aucun stock +dupliqué n'appartient au plan. Conserver une copie bornée du plan +validé et de sa révision évite qu'une mise à jour du catalogue transforme un +chantier en cours ou un bâtiment déjà posé. Les limites actuelles de `BuildPlan` +restent le point de départ, pas une promesse de gros plans sans limite. + +Proposition de règles : aucune transformation automatique des établis existants ; +la projection n'écrase jamais le terrain ; suivi suspendu hors chunks chargés, +sans forcer leur chargement. Une casse du Métabli ou une annulation du projet +ne supprime aucun bloc déjà posé et ne restitue pas de matériaux déjà utilisés. +Les projets complets ne déclenchent pas une deuxième production. +L'activation d'une machine reconnue reste une action explicite, après contrôle +serveur, selon les règles de la clé. Le suivi partagé doit persister le plan, +pas une copie ancienne du monde à rejouer après reconnexion. + +Ces règles et ce registre sont un cadrage, **pas une migration approuvée ni un +format livré**. La forme du Métabli, les droits de gestion, le rayon d'ancrage +et les déblocages restent à fixer avant le lot concerné. + +## Lots proposés + +1. Métabli ouvrable, interface commune, statues et bibliothèque actuelles, + sélection d'un plan et projection à l'emplacement voulu. Aucun bouton pause. +2. Chantier manuel partagé et persistant ; liste correcte des matériaux, + synchronisation du suivi et vérifications de reprise/annulation. +3. Catalogue initial de machines et bâtiments validés, assemblage final des + machines existantes et protocole d'ajout de contenu en datapack. +4. Modules de fonction supplémentaires lorsque leur gameplay est défini, + notamment le commerce ; hors premier lot. + +Chaque lot devra avoir un résultat jouable, un contrat de sauvegarde s'il en +crée un, les libellés FR/EN et les vérifications client/serveur adaptées. +La validation du chantier manuel devra notamment couvrir deux joueurs, une +pose qui consomme réellement l'objet natif, un obstacle ajouté pendant la +construction, une porte/un lit, un circuit, le déchargement, la reconnexion et +la casse du Métabli sans disparition des blocs construits. diff --git a/docs/packwiz.md b/docs/packwiz.md index 3c4d537..917e2e7 100644 --- a/docs/packwiz.md +++ b/docs/packwiz.md @@ -13,6 +13,100 @@ JAR Sanctuary sont des pièces jointes de releases Gitea. Aucun JAR de mod ou dépendance téléchargée n'entre dans l'historique Git. Le canal Beta est distinct de l'ancien pack 26.2. +## beta.110 — version commune + +Clay Workshop, Statuaire et import GLB intégrés aux œufs et à la neige saisonnière. +[Contrat, vérifications et suivi de mise à jour](clay-workshop-integration-beta110.md). +Archives normal/Test vérifiées. Publication du canal et synchronisation de +l’instance Sanctuary Beta demandées ; leur résultat est consigné dans le ticket. + +## beta.105 — archives locales + +Saisons et neige, chapeaux vivants, molette du fût et panoramas avec shader. +[Contrat, tests et empreintes](living-hats-seasons-beta105.md). +Archives normales/Test construites en copie isolée pour préserver le chantier +beta.106 simultané. Aucun déploiement ni publication du canal. + +## beta.104 — archives locales + +Contraste des argiles grise et noire et rangement beta.103. +[Validation](clay-contrast-beta104.md). Packs normal/Test locaux, aucun déploiement. + +## beta.103 — archives locales + +Rangement créatif par séries de 16. [Détails](colored-bricks-order-beta103.md). +Packs normal/Test locaux ; aucune publication ni installation personnelle. + +## beta.102 — archives locales + +Briques et argiles des seize couleurs, Minecraft 26.3. Les nouvelles textures +sont embarquées dans le mod ; le resource pack beta.090 reste compatible. +[Contrat et validation](colored-bricks-beta102.md). +Aucune publication du canal ni installation personnelle. + +## beta.101 — archives locales + +Correctif d’atterrissage des familiers volants, Minecraft 26.3, resource pack +beta.090 inchangé. [Validation](flying-landing-beta101.md). +Aucune publication du canal ni installation personnelle. + +## beta.100 — archives locales + +`Sanctuary-beta.100.mrpack` et `Sanctuary-Test-beta.100.mrpack` : statues +conditionnées aux découvertes et promenade des familiers. Minecraft 26.3, +resource pack beta.090 inchangé. Voir [validation](exploration-familiers-beta100.md). +Aucun canal publié ni déploiement personnel pour cette livraison. + +## beta.099 — Catalogue et Progression + +Packs normal/Test locaux pour Minecraft 26.3 finale. +[Contrat, génération et vérifications](catalogue-progression-beta099.md). +Les anciennes salles sauvegardées restent inchangées. Aucun canal ou instance +personnelle modifié. + +## beta.098 — Construction créative + +Packs normal/Test locaux pour Minecraft 26.3 finale. Ajout du bouton créatif +pour construire le plan du Métabli. [Contrat](creative-construction-beta098.md). +Aucun canal ni instance personnelle modifié. + +## beta.097 — Temples et plans texturés + +Packs normal/Test locaux pour Minecraft 26.3 finale. Présence des temples de +secours et aperçu avec les textures du pack actif. +[Contrat et vérifications](temple-crash-beta097.md). +Le rapport initial concerne beta.089 sur 26.3-pre-2 ; le correctif cible la +version 26.3 finale du dépôt. Aucun canal ni instance personnelle modifié. + +## beta.096 — Métabli + +Les packs locaux normal/Test ajoutent l’atelier et séparent son catalogue des +commandes de construction accessibles avec K. Minecraft 26.3, dépendances et +pack intégré beta.090 conservés. [Détails et vérifications](metabli-beta096.md). +Aucun canal publié ni instance personnelle modifié. + +## beta.095 — Texture de la tige des super pistons + +Les packs locaux normal/Test corrigent quatre modèles de la tige centrale. +Minecraft 26.3, dépendances et pack intégré beta.090 conservés. +[Détails et vérifications](piston-shaft-beta095.md). +Aucun canal publié ni instance personnelle modifié. + +## beta.094 — Fourneau allumé + +Les packs locaux normal/Test ajoutent les braises de façade et corrigent les +particules du Fourneau. Minecraft 26.3, dépendances et pack de textures intégré +beta.090 conservés. [Détails et vérifications](fourneau-allume-beta094.md). +Aucun canal publié ni instance personnelle modifié. + +## beta.093 — Recherche créative + +Les packs locaux normal/Test beta.093 corrigent l’accès graphique depuis les +infobulles indexées en arrière-plan. Cible Minecraft 26.3 finale ; le rapport +initial provenait de beta.089 sur 26.3-pre-2. Dépendances et textures inchangées. +[Diagnostic et vérifications](tooltip-crash-beta093.md). Aucun canal publié +ni instance personnelle modifié. + ## beta.092 — Clic molette des super pistons Les packs locaux normal/Test beta.092 corrigent la sélection des composants @@ -1766,3 +1860,32 @@ sauvegardes personnelles sont restées fermées pendant les essais et la synchro Références officielles : [installation packwiz](https://packwiz.infra.link/tutorials/installing/packwiz-installer/), [commandes Prism](https://prismlauncher.org/wiki/help-pages/custom-commands/), [bootstrap v0.0.3](https://github.com/packwiz/packwiz-installer-bootstrap/releases/tag/v0.0.3). + +### beta.107 — Œufs, essai isolé + +Archives locales normal/Test vérifiées : [contrat](spawn-eggs-acquisition-beta107.md). +Construites sur beta.105 dans `sanctuary-eggs-beta107` ; le Statuaire beta.106, +en développement parallèle, n’est pas embarqué. Les sources des œufs sont aussi +présentes dans le répertoire principal. Aucun canal, instance Prism, serveur ou +sauvegarde personnelle mis à jour. + + +### beta.108 — Neige en volume, essai isolé + +Archives locales normal/Test vérifiées : [contrat](snow-accumulation-beta108.md). +Construites sur beta.107 dans `sanctuary-snow-beta108`, avec les œufs et un +plafond de neige confirmé à deux blocs. Statuaire beta.106 reste dans sa livraison +séparée. Sources de neige aussi présentes dans le répertoire principal, sans +modifier ses compteurs beta.106. Aucun canal, instance Prism, serveur ou +sauvegarde personnelle mis à jour. + + +### beta.109 — Fonte saisonnière, essai isolé + +Archives locales normale/Test vérifiées. [Contrat et vérifications](seasonal-snow-melt-beta109.md). Base beta.108, avec +accumulation et œufs ; Statuaire beta.106 reste une livraison séparée. Les +nouveaux dépôts sont suivis dans les chunks et fondent progressivement selon +la saison. Les blocs antérieurs et les constructions sont conservés. +Assemblage dans `sanctuary-melt-beta109` ; sources aussi intégrées au répertoire +principal sans modifier son chantier et ses compteurs beta.106. +Aucune publication du canal, installation Prism ou sauvegarde personnelle. diff --git a/docs/piston-shaft-beta095.md b/docs/piston-shaft-beta095.md new file mode 100644 index 0000000..48c03b8 --- /dev/null +++ b/docs/piston-shaft-beta095.md @@ -0,0 +1,48 @@ +# beta.095 — Texture du pilier des super pistons + +La tige centrale utilise seulement le carré de bois central de la grande plaque +`sanctuary:block/super_piston/top`, pour les super pistons normaux et gluants. +Le prélèvement reste dans les pixels 16 à 32 de l'image originale de 48 × 48, +sans les bordures. Les UV suivent les dimensions réelles du pilier, à un pixel +par unité de modèle, sans étirement. Cela couvre aussi les segments courts dans +le socle ouvert et derrière la tête. + +Les grandes plaques, le corps et les textures originales fournies restent +inchangés. La course, les collisions et les six orientations ne changent pas. +Seuls quatre modèles JSON et leur générateur sont modifiés ; aucun code Java, +identifiant ou format de sauvegarde n'évolue. La référence à la texture +Sanctuary respecte le pack de ressources actif. Aucune nouvelle image ni dépendance ajoutée. + +Régénération : `python3 tools/generate-super-piston-models.py`. + +## Vérifications et livraison + +Le parcours client natif `Piston087ClientChecks` a réussi en 1 min 22 s +(`build/piston095-client.log`), dans un nouveau monde plat de développement, +graine 87 : six directions, normal/gluant, déplacement et retour, collisions, +charges de 108/109, obstacles, inventaires, adhérence, déchargement/rechargement +et démontage. Aucun avertissement de référence de texture manquante. + +Les captures horizontales et verticales ont été inspectées dans +`build/piston095-evidence/`, notamment `0015_piston087-normal-face-open.png` +et `0001_piston087-normal-up.png`. Le bois est à la même densité que la plaque. + +Le contrôle des quatre modèles vérifie que tous les prélèvements restent dans +le carré central 16–32 pixels et que leurs dimensions en pixels correspondent +exactement à celles de leurs faces, raccords courts inclus. Géométrie, autres +faces et textures originales identiques à beta.094. Le générateur est reproductible. + +`check build assemblePack assembleTestPack -x :sanctuary:runGameTest` a réussi +en 2 min 28 s, 124 tâches (`build/piston095-check.log`). +Les deux archives normal/Test ont été exportées et vérifiées : version beta.095, +Minecraft 26.3, même JAR Sanctuary embarqué. Les classes Java, les autres assets, +JEI et les archives beta.094 sont inchangés. Le reçu et les empreintes sont dans +`build/piston095-artifact.json` ; `build/verify-piston095.py` reproduit ces contrôles. +`git diff --check` passe. + +[Pack normal](../build/Sanctuary-beta.095.mrpack) · +[Pack test](../build/Sanctuary-Test-beta.095.mrpack). + +Le serveur dédié GameTest est exclu (`-x :sanctuary:runGameTest`) pour respecter +le choix antérieur de tests client uniquement. Aucun déploiement dans une +instance personnelle, changement de monde ou publication du canal packwiz. diff --git a/docs/seasonal-snow-melt-beta109.md b/docs/seasonal-snow-melt-beta109.md new file mode 100644 index 0000000..edbdb01 --- /dev/null +++ b/docs/seasonal-snow-melt-beta109.md @@ -0,0 +1,94 @@ +# SNOW-109 — Fonte saisonnière + +Branche `codex/seasonal-snow-melt-beta109`, base beta.108, Minecraft 26.3. +État : livraison locale vérifiée. Demande : ajouter la fonte saisonnière. + +## Comportement livré + +Neige déposée par la météo Sanctuary : retrait progressif d'une couche de haut +en bas, bloc plein puis sept couches, jusqu'au sol. Deux blocs maximum pour +l'accumulation restent la valeur par défaut confirmée. La fonte est active +avec la météo Real Time, suspendue en hiver et sous un profil neige/averses de +neige, même pendant ses pauses. Les biomes naturellement froids conservent +leur neige. Printemps : une chance sur deux par sondage météo natif ; été : +chaque sondage ; automne : une chance sur quatre. Aucun rattrapage hors ligne, +ni chargement forcé de chunks ; seuls les sommets exposés des chunks simulés. +À 20 ticks/s et `randomTickSpeed = 3`, une couche fond en moyenne en 3,4 minutes en été, +6,8 au printemps et 13,7 en automne. Pas d'eau ni de butin lors de la fonte. +Une règle serveur `sanctuary:seasonal_snow_melt`, active par défaut, permet +d'arrêter la fonte sans désactiver la neige. + +## Contrat de données et migration, avant implémentation + +Ajout d'une attache Fabric persistante par chunk, +`sanctuary:natural_snow_v1`, sans modifier les formats existants. Elle mémorise +les positions des nouveaux dépôts avec le nombre de couches antérieures et +le nombre attendu après dépôt (0 à 8). Les blocs restent les blocs Minecraft +`minecraft:snow` et `minecraft:snow_block` ; pas de nouvel identifiant de bloc. +L'absence de données signifie aucun dépôt suivi. Aucune recherche rétrospective, +conversion ou réécriture de terrain au chargement : la neige d'avant beta.109 +est conservée car son origine n'est pas connue. Les sauvegardes personnelles +restent fermées pendant le développement. Graine de test : 109. + +Seules les couches ajoutées par les précipitations depuis beta.109 fondent. +La neige construite, la neige générée et les couches antérieures à un dépôt +sont préservées. Une modification extérieure du bloc (casse, placement, +piston, commande) retire son suivi : le remplacement ne doit jamais hériter +d'une autorisation de fonte. Une incohérence de suivi est oubliée sans fonte. +Les informations suivent le cycle natif de sauvegarde/déchargement du chunk, +sans fichier global ni lecture disque par colonne. Le retrait du mod laisse +des blocs de neige natifs ; aucun effacement n'est lancé par migration. + +## Vérifications ciblées + +Le client natif lance les contrôles d'accumulation beta.108 (deux mondes neufs), +puis un monde neuf de graine 109 pour la fonte et le recharge après fermeture. +Les quatre saisons utilisent une horloge de test déterministe, sans commande +supplémentaire dans le mod livré. Les sondages appellent le véritable +`ServerLevel.tickPrecipitation`, sans accélération ajoutée au jeu distribué. + +Cas vérifiés : bloc plein vers sept couches, fonte de haut en bas jusqu'au sol, +neige construite et couches anciennes, dépôt météo sur une construction, +casse/repose et ajout manuel de couches, toit et capuchon de neige manuel, +activation/désactivation par gamerule, les quatre saisons, profils neige et +pauses d'averses, biome froid et mode Vanilla, sauvegarde native des deux blocs +suivis et de la gamerule, puis fonte après rechargement. + +Le choix du sommet utilise la heightmap native mise à jour immédiatement, +sans dépendre d'une propagation de lumière encore en attente après la fonte. +Le suivi est invalidé après toute modification effective de bloc ; chaque +nouveau dépôt ou retrait météo enregistre ensuite uniquement ses couches. +Les instantanés de suivi sont immuables pour la sauvegarde asynchrone des chunks. + +## Livraison — 17 septembre 2026 + +- `./gradlew check build assemblePack assembleTestPack -x :sanctuary:runGameTest` : + réussi, 125 tâches, 2 min 20 s. +- `./gradlew :sanctuary:runClientGameTest -PsanctuaryClientTests=true -PsanctuaryMelt109ClientTests=true -PsanctuaryClientNoVsync=true -PsanctuaryQuickTests=true` : + réussi, 54 s ; marqueurs `SNOW108_NATIVE_PASS` et `MELT109_NATIVE_PASS`. +- Java 25. Le GameTest sur serveur dédié reste exclu conformément au refus + antérieur de son EULA ; les essais natifs utilisent le serveur intégré. +- Archives ZIP, métadonnées, sources embarquées, mixins et traductions vérifiés + contre l'archive immuable beta.108. Deux classes existantes modifiées, trois + nouvelles classes plus la classe synthétique du switch de saison. Aucun + test inclus dans le JAR. Assets identiques, sauf deux nouveaux libellés FR/EN. + +[Pack normal](../build/Sanctuary-beta.109.mrpack) · +[Pack Test](../build/Sanctuary-Test-beta.109.mrpack). + +| Artefact | SHA-256 | +| --- | --- | +| Normal | `4f80292b97398e993d05e831682c879d2e6b40e7ab8cb385a15142b68c2d3ba4` | +| Test | `c0c7363ec3a2a8c568b77692b65dd8a4138c247bc928adecd5626f83bb5097bb` | +| JAR Sanctuary | `0b53f5c2d9b50890067c42a1c71b84bf70f0c6af90c876020955bbe426ff6ffa` | + +Reçu : `build/melt109-artifact.json`. Journaux : `build/melt109-native.log`, +`build/melt109-check-build.log`. Manifeste : `build/melt109-source-manifest.json`. + +Assemblage isolé sur beta.108, avec accumulation et œufs beta.107 ; Statuaire +beta.106 reste livré séparément. Sources intégrées au répertoire principal en +préservant ses autres travaux et ses compteurs beta.106 ; le worktree de +livraison utilise beta.109 dans les deux compteurs et le manifeste packwiz. +Aucun tag, publication du canal, déploiement, installation Prism ou monde +personnel modifié. Aucun essai Windows. La fonte n'est pas appliquée +rétrospectivement à la neige ancienne dont l'origine est inconnue. diff --git a/docs/snow-accumulation-beta108.md b/docs/snow-accumulation-beta108.md new file mode 100644 index 0000000..a7bc17c --- /dev/null +++ b/docs/snow-accumulation-beta108.md @@ -0,0 +1,115 @@ +# SNOW-108 — Accumulation de neige en volume + +Branche `codex/snow-accumulation-beta108`. Base livrée beta.107, Minecraft 26.3. +État : livraison locale vérifiée. Statuaire beta.106 reste une livraison séparée. + +## Contrat avant modification + +Demande : la neige doit s’accumuler et occuper réellement des blocs. +Les précipitations déposent une couche à la fois ; la huitième devient un bloc +natif de neige solide. Le dépôt suivant commence au-dessus. Même cadence de +précipitation que Minecraft, uniquement dans les chunks simulés pendant une +chute de neige, y compris les averses Sanctuary et les biomes froids vanilla +lorsque les règles Sanctuary sont actives. + +Valeur confirmée par le créateur : deux blocs de hauteur totale par défaut, +réglables côté serveur. +Le plafond compte la colonne contiguë de neige déjà présente (couches et blocs), +jusqu’au premier support d’une autre matière. Zéro désactive l’accumulation, +moins un permet de continuer jusqu’à la limite de hauteur du monde. + +La lumière, les supports, la place libre et les biomes acceptables restent +contrôlés par Minecraft. Ni plantes, ni équipements, ni eau, ni constructions +ne sont remplacés. La croissance pousse les entités vers le haut comme le dépôt +natif, avec la nouvelle collision. Les chaudrons gardent leur fonctionnement. + +## Données et compatibilité + +Uniquement des blocs natifs `minecraft:snow` et `minecraft:snow_block` : collision, +minage, butin et sauvegarde natifs. Pas de bloc, BlockEntity ou format propriétaire. +La nouvelle gamerule `sanctuary:snow_accumulation_height` utilise la sauvegarde +native des règles, sans réécriture des règles préexistantes. Elle s’ajoute avec +sa valeur par défaut quand absente. Aucune sauvegarde personnelle ouverte, aucun +balayage de chunks, aucune expansion ni conversion de terrain au chargement. +Seuls les dépôts futurs pendant le jeu, autorisés par la demande, changent. + +Les blocs de neige pleins suivent le comportement Minecraft : ils ne fondent pas +comme les fines couches éclairées. Cette livraison n’ajoute pas une fonte +saisonnière qui pourrait effacer des constructions en neige. + +La valeur native `minecraft:max_snow_accumulation_height = 0` reste un arrêt +prioritaire. Dans un monde Sanctuary, toute valeur native positive autorise le +dépôt volumétrique, dont le plafond est la nouvelle règle en blocs. Hors Sanctuary, +le fonctionnement et la limite vanilla sont conservés. + +## Utilisation + +- Valeur par défaut : `/gamerule sanctuary:snow_accumulation_height 2`. +- Quatre blocs : `/gamerule sanctuary:snow_accumulation_height 4`. +- Arrêt des nouveaux dépôts : valeur `0`. +- Sans plafond particulier, sauf celui du monde : valeur `-1`. +- Valeurs admises : de `-1` à `128`, positives exprimées en blocs complets. + +Réduire le plafond ne supprime pas la neige déjà présente. La limite native +`minecraft:max_snow_accumulation_height` n’est pas réécrite automatiquement. +Les noms et descriptions de la nouvelle règle sont traduits en FR/EN dans +l’interface native de création du monde. + +## Essais natifs + +Nouveaux mondes plats de développement, graine `108`, un avec Sanctuary et +un sans. Le test appelle le véritable `ServerLevel.tickPrecipitation` : + +- Sept couches successives, huitième dépôt en `snow_block`, collision pleine. +- Un cochon debout sur la neige monte avec le nouveau volume. +- Empilement sur le bloc formé ; arrêt à deux blocs ; commande à quatre blocs, + puis mode sans plafond et six blocs atteints. +- Arrêt à zéro sur la règle Sanctuary et priorité du zéro sur la règle native. +- Dépôt pendant les averses, aucun dépôt pendant leurs pauses ni sous pluie tiède. +- Fleur et eau libre préservées, neige sur le toit et non dans la pièce ; + lumière propagée par un bloc lumineux empêchant le dépôt. +- Le chaudron continue de recevoir sa neige poudreuse native. +- Météo vanilla, biome froid et Sanctuary actif : accumulation volumétrique. +- Second monde sans Sanctuary : une couche native même après 40 tentatives. + +La décision d’activation est mise en cache au démarrage du serveur, d’après le +choix Sanctuary fixé à la création. Aucun accès disque par colonne de pluie. +Le comptage de profondeur s’arrête dès le plafond atteint ; le mode illimité +n’a pas besoin de compter. Pas de boucle ajoutée sur les chunks ou les joueurs. + +Les tests ne changent aucune sauvegarde personnelle. Les dépôts sont déclenchés +explicitement pour valider leur résultat ; aucun facteur d’accélération météo +n’est ajouté au code livré. + +## Livraison et vérifications — 17 septembre 2026 + +Validation dans le worktree `sanctuary-snow-beta108`, sur la base immuable +beta.107. Les œufs de beta.107 sont inclus ; les sources Statuaire beta.106 +présentes dans le répertoire principal restent préservées, mais ne sont pas +incluses dans ces archives. Les compteurs de ce worktree sont synchronisés à +beta.108 ; ceux du répertoire principal restent attachés à son chantier beta.106. + +- `./gradlew check build assemblePack assembleTestPack -x :sanctuary:runGameTest` : + réussi, 125 tâches, 3 min 34 s. +- `./gradlew :sanctuary:runClientGameTest -PsanctuaryClientTests=true -PsanctuarySnow108ClientTests=true -PsanctuaryClientNoVsync=true -PsanctuaryQuickTests=true` : + réussi, 1 min 17 s, marqueur `SNOW108_NATIVE_PASS`. +- Java 25 ; GameTest dédié exclu conformément au refus antérieur d'accepter son + EULA. Les essais ci-dessus utilisent le serveur intégré dans deux mondes neufs. +- Intégrité ZIP, versions Minecraft/mod, JAR embarqués, sources, mixins et libellés + vérifiés. Par rapport au JAR beta.107 : seulement `WeatherService.class` + modifié et les deux classes d'accumulation ajoutées ; assets inchangés sauf + les deux nouveaux libellés de règle FR/EN. Aucun test embarqué dans le JAR livré. + +Archives locales : [pack normal](../build/Sanctuary-beta.108.mrpack) et +[pack Test](../build/Sanctuary-Test-beta.108.mrpack). + +| Artefact | SHA-256 | +| --- | --- | +| Normal | `18f2b0c6a12c3618a9b70e22999b89697a41034e86bc1aa33f0c9d19caadd0c2` | +| Test | `66f9395cd47b031ca52162081c397da7a5ab7fdfa3e236dec5110dbcf95306bc` | +| JAR Sanctuary | `dc2b026cd0b6475e512283f72225593666fd60a3a265b8e91bf6bdae9f875bb4` | + +Reçu : `build/snow108-artifact.json`. Journaux : `build/snow108-native.log`, +`build/snow108-check-build.log`. Manifeste : `build/snow108-source-manifest.json`. +Aucun tag, publication, canal packwiz, installation Prism ou monde personnel +modifié. Pas d'essai Windows ; pas de fonte saisonnière des blocs pleins. diff --git a/docs/spawn-eggs-acquisition-beta107.md b/docs/spawn-eggs-acquisition-beta107.md new file mode 100644 index 0000000..e1c2f81 --- /dev/null +++ b/docs/spawn-eggs-acquisition-beta107.md @@ -0,0 +1,107 @@ +# EGGS-107 — Naissances en œufs et récupération des générateurs + +Branche `codex/eggs-beta107`. Travail isolé du chantier Statuaire beta.106. +Base vérifiée : sources de la livraison beta.105. État : livré localement, vérifié ; aucun déploiement ni publication. + +## Contrat + +Dans les mondes où Sanctuary est actif, une reproduction réussie remet un +œuf natif de l'espèce au sol au lieu d'ajouter directement le nouveau mob. +L'enfant est calculé par Minecraft : croisements, variantes, propriétaire et +caractéristiques héritées sont conservés. Les délais, nourriture, statistiques +et progrès restent ceux de la reproduction native. L'œuf d'un animal qui a un +stade bébé fait naître un bébé, y compris avec un distributeur. + +Grenouille : œuf de têtard, pour conserver son cycle naturel. Tortue et sniffer : +œuf d'apparition de bébé à la place de la ponte. Allay : duplication contre +améthyste et musique, avec le délai natif conservé. Villageois : nourriture et +lit libre restent requis ; le lit réservé est libéré tant que l'œuf est stocké. + +Un générateur classique réellement cassé par un joueur en survie, avec un outil +adapté sans Toucher de soie, remet un seul œuf correspondant à son prochain mob +configuré. Fortune ne multiplie pas ce résultat. Ni explosion, ni annulation de +casse, ni mode créatif ne produisent cet œuf. Les générateurs d'épreuves ne sont +pas transformés en source d'œufs par cette règle. + +Les identifiants natifs des items restent inchangés. Les données de naissance +utilisent les composants d'item natifs ENTITY_DATA et CUSTOM_DATA ; aucune +migration de sauvegarde, régénération ou ouverture de monde personnel. +Les naissances et pontes déjà présentes restent en place. Aucune modification +hors des mondes Sanctuary ; un œuf déjà obtenu garde ses données de naissance. + +Un catalogue distinct des 88 espèces sépare les voies livrées des propositions +à valider. Les méthodes spéciales restantes ne sont pas annoncées comme livrées. + +## Catalogue + +[Les 88 espèces et leurs voies](spawn-eggs-catalogue-beta107.md) : 28 par +reproduction ou duplication, 7 par les générateurs classiques vanilla et +53 propositions. Réponse du créateur : **catalogue à valider d’abord**. +Aucune des 53 propositions n’est implémentée dans ce ticket. + +## Vérifications natives + +Nouveau monde de test plat, graine `107`, Minecraft 26.3 / Fabric 0.160.5+26.3, +Java 25, macOS. Le test utilise le vrai client et son serveur intégré : + +- Naissance native de 22 espèces via `Animal.spawnChildFromBreeding` : un seul + œuf, aucun enfant vivant ajouté, âge de bébé stocké, délai des deux parents. +- Cheval × âne → mule ; moutons rouge × bleu → bébé violet, à la pose et au clic + sur un adulte ; loup apprivoisé → bébé encore lié au même propriétaire. +- Sérialisation/relecture de l’item avec le codec natif ; position, UUID et + réservations de cerveau absents ; éclosion en survie consomme un œuf. +- Vrai comportement de distributeur : bébé et couleur héritée. +- Grenouille sans seconde ponte, tortue sans état de ponte, renifleur sans œuf + bloc supplémentaire ; allay avec délai conservé ; villageois avec résultat + vide pour que l’appelant rende le lit réservé, bébé à l’éclosion. +- Existence native des 88 œufs ; destruction réelle d’un générateur par le + contrôleur joueur avec pioche normale/Fortune/Toucher de soie, main vide, + créatif et règle de drops de blocs désactivée. +- Deuxième monde neuf, Sanctuary désactivé à la création : bébé de vache vivant, + aucun œuf et générateurs sans nouveau butin. + +Les appels natifs de naissance sont testés directement ; il ne s’agit pas d’un +élevage de chaque espèce laissé tourner pendant plusieurs minutes. Les tests +n’effectuent pas de parcours complet de raid, donjon ou guérison : ces voies +spéciales sont des propositions. Aucun monde personnel modifié. + +## Binaire et distribution + +`./gradlew check build assemblePack assembleTestPack -x :sanctuary:runGameTest` +réussit : **125 tâches, 3 min 44 s**. Le serveur GameTest dédié reste exclu, +l’acceptation de son EULA n’étant pas autorisée. Les tests natifs ci-dessus +utilisent le serveur intégré ; dernier passage **29 s**, marqueur +`EGGS107_NATIVE_PASS`, après les derniers changements de code. + +Commande des essais : + +```sh +./gradlew :sanctuary:runClientGameTest -PsanctuaryClientTests=true \ + -PsanctuaryEggs107ClientTests=true -PsanctuaryClientNoVsync=true \ + -PsanctuaryQuickTests=true +``` + +L’assemblage et les essais se font dans le worktree voisin +`sanctuary-eggs-beta107`, branche `codex/eggs-beta107`, sur les sources vérifiées +de beta.105. Les changements propres aux œufs sont aussi intégrés au répertoire +principal sans réécrire le code ni les compteurs en cours du chantier beta.106. +**Ces archives ne contiennent pas le Statuaire beta.106 en cours de validation.** +Les compteurs du worktree et du pack livré sont tous `beta.107`. + +Comparaison binaire avec beta.105 : neuf classes ajoutées, aucune ancienne classe +modifiée. Les seules ressources graphiques/données différentes sont les deux +fichiers de langue FR/EN, avec deux nouveaux libellés ; le pack de textures, +les recettes et la génération restent byte pour byte identiques. Les mixins +ajoutés figurent dans le manifeste. Sources/JAR, ZIP, versions, dépendances, +88 lignes uniques du catalogue et empreintes sont vérifiés. + +- [Pack normal](../build/Sanctuary-beta.107.mrpack) — SHA-256 + `1b92c5abcc07be9c36d507d2ffdaf6d420a541c6624f105f8643594a02884600`. +- [Pack de test](../build/Sanctuary-Test-beta.107.mrpack) — SHA-256 + `db9e32591cb263667b397fdb78738fbe45f58689b5574f2cb483f076635e7eef`. + +Logs et reçus ignorés : `build/eggs107-check-build.log`, +`build/eggs107-native.log`, `build/eggs107-artifact.json`, +`build/eggs107-source-manifest.json`. Les fichiers sources des tests sont inclus +dans le dépôt ; aucune classe de test n’est embarquée dans le mod livré. +Aucun test Windows, installation Prism, publication du canal, commit ou tag. diff --git a/docs/spawn-eggs-catalogue-beta107.md b/docs/spawn-eggs-catalogue-beta107.md new file mode 100644 index 0000000..ea4041e --- /dev/null +++ b/docs/spawn-eggs-catalogue-beta107.md @@ -0,0 +1,155 @@ +# Acquisition des 88 œufs — catalogue à valider + +Demande du créateur : **valider les voies spéciales avant de les implémenter**. +Ce catalogue utilise les 88 identifiants de `CompanionType`, pour Minecraft 26.3. +Les œufs natifs servent aussi aux familiers. Aucun nouvel œuf propriétaire. + +**28 espèces** ont une voie par reproduction/duplication dans EGGS-107, +**7** par les générateurs classiques des structures vanilla, +et **53** attendent une voie spéciale. + +Les structures doivent être présentes et accessibles dans la partie : cette +livraison n’ajoute aucun donjon, ne régénère aucun chunk et ne garantit pas ces +structures dans un monde plat. Un générateur classique configuré autrement +remet aussi l’œuf de son type, s’il existe ; cela ne crée pas une voie naturelle +pour toutes les espèces. Les générateurs d’épreuves ont une proposition distincte. + +## Voies intégrées à EGGS-107 + +Une reproduction donne **un œuf**, jamais simultanément un bébé. Les règles +natives d’accouplement, d’apprivoisement, de nourriture et de délai restent +applicables. Les poissons crus ne remplacent pas un seau quand le jeu exige un +poisson vivant. Les œufs de bébé restent bébés après stockage ; le temps de +croissance commence à l’éclosion. + +| Espèce (identifiant natif) | Obtention | +| --- | --- | +| `cat` | Deux chats apprivoisés, morue ou saumon crus. | +| `chicken` | Deux poules, graines. | +| `cow` | Deux vaches, blé. | +| `donkey` | Deux ânes apprivoisés, carottes ou pommes dorées. | +| `fox` | Deux renards, baies sucrées ou lumineuses ; confiance héritée. | +| `horse` | Deux chevaux apprivoisés, carottes ou pommes dorées ; caractéristiques héritées. | +| `llama` | Deux lamas apprivoisés, bottes de paille ; force et variante héritées. | +| `mule` | Croisement cheval × âne. La mule reste stérile. | +| `pig` | Deux cochons, carotte, pomme de terre ou betterave. | +| `rabbit` | Deux lapins, carotte, carotte dorée ou pissenlit. | +| `sheep` | Deux moutons, blé ; mélange de couleurs natif. | +| `tadpole` | Deux grenouilles, boules de slime ; œuf de têtard à la place de la ponte aquatique. | +| `armadillo` | Deux tatous, yeux d’araignée. | +| `bee` | Deux abeilles, fleurs. | +| `camel` | Deux dromadaires, cactus. | +| `goat` | Deux chèvres, blé. | +| `ocelot` | Deux ocelots, morue ou saumon crus. | +| `panda` | Deux pandas, bambou et environnement de reproduction natif ; gènes hérités. | +| `strider` | Deux arpenteurs, champignons biscornus. | +| `turtle` | Deux tortues, herbes aquatiques ; œuf de bébé à la place de la ponte. | +| `villager` | Reproduction native des villageois : nourriture suffisante et lit libre accessible. L’œuf donne un bébé. | +| `wolf` | Deux loups apprivoisés, nourriture native ; propriétaire hérité. | +| `allay` | Duplication native : allay dansant avec un jukebox, éclat d’améthyste. Délai de 5 minutes conservé. | +| `axolotl` | Deux axolotls, poissons tropicaux en seau. | +| `hoglin` | Deux hoglins, champignons carmin. | +| `mooshroom` | Deux champimeuhs, blé. | +| `nautilus` | Deux nautiles apprivoisés, nourriture acceptée par la reproduction native. | +| `sniffer` | Deux renifleurs, graines de plante torche ; œuf d’apparition à la place de l’œuf bloc. | + +Pour les suivants : casser le générateur avec un outil adapté, sans Toucher de +soie. Un œuf par générateur ; Fortune ne multiplie pas le résultat. Les outils +avec Toucher de soie ne donnent pas d’œuf ; cette livraison ne leur ajoute pas +non plus une récupération du bloc générateur. + +| Espèce (identifiant natif) | Source vanilla si présente | +| --- | --- | +| `skeleton` | Générateur de donjon. | +| `spider` | Générateur de donjon. | +| `zombie` | Générateur de donjon. | +| `cave_spider` | Générateur de mine abandonnée. | +| `blaze` | Générateur de forteresse du Nether. | +| `magma_cube` | Générateur de salle au trésor de bastion. | +| `silverfish` | Générateur de la salle du portail du stronghold. | + +## Propositions — pas encore implémentées + +Les pourcentages ci-dessous sont des valeurs de départ à valider. Les captures +**convertissent le mob en œuf** et ne laissent pas son double dans le monde. +Pour les butins et récompenses, un mob né d’un œuf ne pourra pas produire son +propre œuf par cette voie. Les récompenses uniques devront avoir un suivi serveur +persistant : contrat de migration à définir lors du prochain ticket. Ces règles +ne sont pas encore ajoutées au jeu. + +| Espèce | Type de voie | Proposition concrète | +| --- | --- | --- | +| `bat` | Interaction | Capturer une chauve-souris endormie avec un sac et une boule de slime. Le mob devient un œuf ; sac rendu. | +| `cod` | Capture | Morue en seau + œuf de poule : convertir le poisson en œuf d’apparition ; seau vide rendu. | +| `frog` | Interaction | Élever un têtard jusqu’à la métamorphose, puis donner une boule de slime : un œuf de grenouille, une seule fois par grenouille élevée. | +| `salmon` | Capture | Saumon en seau + œuf de poule ; seau vide rendu. | +| `slime` | Butin conditionnel | Tuer un petit slime au corps à corps : 5 % de chance, uniquement sur la génération issue d’un slime naturel. | +| `squid` | Capture | Interagir avec un poulpe sous l’eau avec un sac et une morue : le convertir en œuf ; sac rendu. | +| `tropical_fish` | Capture | Poisson tropical en seau + œuf de poule ; conserver exactement ses couleurs et son motif. | +| `copper_golem` | Construction | Construire un golem de cuivre puis lui faire terminer 16 tris réels : un œuf, une seule fois par golem construit. | +| `dolphin` | Exploration | Nourrir un dauphin et atteindre le trésor qu’il indique : un œuf à la découverte, une fois par trésor et joueur. | +| `drowned` | Butin conditionnel | Noyé naturel tué sous l’eau par un joueur : 5 %. | +| `glow_squid` | Capture | Même capture que le poulpe, dans une caverne immergée ; conversion du mob en œuf lumineux. | +| `husk` | Butin conditionnel | Zombie momifié naturel tué dans le désert en plein jour : 5 %. | +| `parrot` | Interaction | Apprivoiser un perroquet puis lui offrir des graines pendant sa danse au jukebox : un œuf, une seule fois par perroquet sauvage apprivoisé. | +| `pufferfish` | Capture | Poisson-globe en seau + œuf de poule ; seau vide rendu. | +| `polar_bear` | Interaction | Donner du saumon à deux adultes sur neige : reproduction Sanctuary spécifique donnant un œuf de bébé, délai de 5 minutes. | +| `snow_golem` | Construction | Monter le golem de neige natif puis l’emballer avec une citrouille sculptée : conversion du golem en œuf. | +| `bogged` | Épreuve | Après une vague terminée, toucher son générateur d’épreuves avec un œuf de poule : un œuf de bogged, une fois par joueur et recharge native. | +| `parched` | Butin conditionnel | Parched naturel vaincu dans le désert avec un bouclier après avoir bloqué sa flèche : 5 %. | +| `breeze` | Épreuve | Même extraction après victoire sur les vagues de Breeze ; coût d’un œuf de poule, recharge native du générateur d’épreuves. | +| `camel_husk` | Rencontre | Désarçonner les cavaliers hostiles puis apprivoiser la monture avec sa nourriture native : un œuf, une seule fois pour cette monture sauvage. | +| `creeper` | Butin conditionnel | Creeper naturel tué par un squelette, comme pour les disques : 10 %. | +| `endermite` | Interaction | Toucher une endermite apparue d’une perle avec un fruit de chorus : conversion en œuf avant sa disparition. | +| `guardian` | Butin conditionnel | Gardien naturel vaincu à proximité d’un monument : 5 %. | +| `phantom` | Butin conditionnel | Phantom naturel vaincu dans les airs avec une arme de mêlée : 10 %. | +| `piglin` | Troc | Ajouter l’œuf au troc contre un lingot d’or : 2 %, poids exact à équilibrer. | +| `pillager` | Butin conditionnel | Pillard capitaine naturel vaincu par un joueur : 10 %. | +| `sulfur_cube` | Capture | Cube de soufre en seau + œuf de poule : convertir le contenu du seau en œuf, avec taille et contenu conservés. | +| `trader_llama` | Commerce | Offre rare du marchand ambulant : œuf de lama de marchand contre émeraudes et botte de paille. Les lamas ordinaires restent obtenus par reproduction. | +| `wandering_trader` | Commerce | Terminer toutes les offres d’un marchand : une offre finale d’œuf contre 32 émeraudes. Une fois par marchand naturel. | +| `zombie_horse` | Rencontre | Libérer puis apprivoiser un cheval-zombie sauvage avec la nourriture native : un œuf, une seule fois par monture. | +| `zombie_villager` | Guérison | Guérir un villageois zombie naturel : un œuf de villageois zombie remis au soigneur ; un seul par individu, sans boucle de réinfection. | +| `zombified_piglin` | Butin conditionnel | Piglin zombifié naturel vaincu au Nether par un joueur : 5 %. | +| `stray` | Butin conditionnel | Vagabond naturel vaincu pendant une chute de neige : 10 %. | +| `vindicator` | Butin conditionnel | Vindicator vaincu dans un manoir ou un raid : 5 %. | +| `zoglin` | Transformation | Amener un hoglin dans l’Overworld et le voir devenir zoglin : première interaction avec un champignon carmin remet un œuf, une seule fois. | +| `creaking` | Exploration | Neutraliser un grinceur lié puis casser son cœur actif sans Toucher de soie : un œuf. Le cœur ne donne pas simultanément sa version récupérable. | +| `elder_guardian` | Boss | Vaincre un grand gardien du monument : un œuf garanti, uniquement pour les gardiens générés avec le monument. | +| `enderman` | Butin conditionnel | Enderman naturel vaincu dans l’End par un joueur : 5 %. | +| `evoker` | Butin conditionnel | Évocateur vaincu dans un manoir ou un raid : 10 %. | +| `ghast` | Défi | Tuer un ghast naturel en lui renvoyant sa propre boule de feu : 25 %. | +| `happy_ghast` | Élevage | Réhydrater un ghast desséché puis élever le ghastling : premier nourrissage adulte avec une boule de neige remet un œuf de ghastling, une fois par ghast élevé. | +| `iron_golem` | Construction | Construire un golem de fer puis lui offrir un coquelicot et un lingot : conversion volontaire du golem en œuf. | +| `piglin_brute` | Exploration | Piglin barbare du bastion vaincu par un joueur : 10 %. | +| `ravager` | Raid | Dernier ravageur vaincu dans un raid remporté : un œuf au vainqueur, une récompense par raid. | +| `shulker` | Exploration | Shulker d’une cité de l’End vaincu par un joueur : 10 % ; les copies par projectiles ne déclenchent pas cette voie. | +| `skeleton_horse` | Événement | Survivre au piège de cavaliers squelettes puis monter un cheval libéré : un œuf, une récompense par piège. | +| `vex` | Défi | Tuer un vex invoqué après avoir vaincu l’évocateur qui l’a invoqué : 10 %, une récompense maximum par évocateur. | +| `witch` | Butin conditionnel | Sorcière naturelle vaincue pendant qu’elle boit une potion : 10 %. | +| `wither_skeleton` | Butin conditionnel | Wither squelette naturel vaincu dans une forteresse : 5 %, indépendant du crâne. | +| `zombie_nautilus` | Rencontre | Libérer un nautile-zombie de son cavalier puis l’apprivoiser : un œuf, une seule fois par monture sauvage. | +| `ender_dragon` | Boss | Vaincre le dragon invoqué par le cycle natif de l’End : un œuf au participant désigné ; jamais pour un dragon apparu avec un œuf. | +| `warden` | Défi | Vaincre un Warden sorti d’un hurleur naturel : un œuf garanti pour ce combat ; aucun œuf sur un Warden issu d’œuf. | +| `wither` | Boss | Vaincre un Wither invoqué par sable des âmes et crânes : un œuf garanti ; aucun œuf sur un Wither issu d’œuf. | + +## Points à décider ensemble + +- Le principe de conversion avec un sac / un seau pour les espèces capturables. +- La place des défis et du butin rare, et leurs chances exactes. +- Les récompenses de boss garanties, avec exclusion des boss issus d’œuf. +- Les offres du marchand et le troc restent de simples échanges natifs ; aucun + système d’économie, de banque ou de marché n’est introduit ici. + +Cas à ne pas confondre : nourrir ne veut pas toujours dire reproduire. +Le ghast heureux, le dromadaire momifié et le cheval-zombie refusent le mode +amoureux natif ; le nautile-zombie n’a pas de descendance native. Le lama de +marchand suit la reproduction du lama et ne garantit pas un nouvel œuf de +`trader_llama`. Le cube de soufre n’emprunte pas la reproduction d’`Animal`. +Les œufs pondus naturellement par une poule et leur lancer natif restent inchangés : +c’est l’accouplement nourri par le joueur qui produit le nouvel œuf garanti. + +Sources de vérification : catalogue `CompanionType`, classes et données du JAR +Minecraft **26.3** installé (méthodes de reproduction, composants d’items et +aliments natifs), tests intégrés EGGS-107. Le catalogue ne prétend pas qu’un +comportement proposé est déjà une règle vanilla. diff --git a/docs/statuary-beta106.md b/docs/statuary-beta106.md new file mode 100644 index 0000000..1400ef6 --- /dev/null +++ b/docs/statuary-beta106.md @@ -0,0 +1,172 @@ +# STAT-01 — Atelier d’argile et modèles 3D — beta.106 + +Contrat du 17 septembre 2026, branche `codex/statuary-import-beta106`. +Livraison locale vérifiée, cycle natif et archives normale/Test validés. +Aucune publication ni installation personnelle. + +## Parcours retenu avec le créateur + +Un seul dossier utilisateur : `schematics/sanctuary/`. La lecture historique +au premier niveau de `schematics/` reste possible, sans déplacement de fichiers. +Les boutons des deux ateliers ouvrent exactement le même dossier. + +- **Métabli → Bibliothèque** : schémas `.litematic`, `.schem`, `.nbt` et `.schematic` + historiques, avec leurs règles d’import existantes. +- **Métabli → Statues → Mes modèles 3D** : modèles GLB, hauteur 4–96 blocs et + palette. La conversion produit un plan et rejoint les commandes K, matériaux, + export et règles de placement créatif/survie existants. Les statues des + créatures découvertes gardent leur accès habituel. +- **Atelier d’argile** (`sanctuary:clay_workshop`) : choix du GLB et aperçu à + proportions conservées, limité à 16 × 16 × 16 voxels dans un seul bloc. + Un emplacement reçoit l’argile, l’autre fournit la statue. **Un bloc d’argile + est consommé pour chaque statue récupérée**, y compris en créatif. + Les blocs d’argile normale conservent les couleurs du modèle ; les seize + argiles Sanctuary imposent leur couleur unie. Ni boule d’argile ni terre + cuite n’est acceptée. Fermer l’atelier restitue l’argile non consommée. + +Recette de l’atelier : un bloc d’argile au centre supérieur, trois dalles de +pierre lisse au milieu, trois planches en bas. Le bloc utilise les textures +actives de planches, argile et pierre lisse. Le résultat, **Statuaire** +(`sanctuary:statuary`), est un objet transportable et posable. Sa miniature se +voit dans l’inventaire et le monde. Il n’ouvre pas un éditeur lorsqu’on le pose. + +Le fichier se choisit dans la liste ou par dépôt sur l’écran de l’atelier. +« Actualiser » relit le dossier. La rotation proposée à l’atelier d’argile +est un quart de tour. Il n’y a pas d’éditeur de sommets ou d’animations. + +## Identité des modèles et sauvegardes — contrat préalable + +Les identifiants des anciens blocs, formats et générations restent inchangés. +Les nouveaux blocs n’apparaissent que par fabrication ou placement volontaire. +Aucun monde personnel n’est ouvert et aucun chunk existant n’est régénéré. + +Chaque GLB reçoit un identifiant `sanctuary:model/`. +Renommer ou déplacer un fichier ne change pas cet identifiant. Réexporter avec +un contenu binaire différent produit un nouvel identifiant. Les différentes +argiles et rotations du même modèle conservent l’identité de la source. +Il s’agit d’une identité de modèle dans les données, et non d’un nouvel +identifiant de bloc enregistré pour chaque import. Elle ne donne aucun droit +serveur et ne désigne ni une URL ni un chemin à ouvrir. + +Le statuaire a une BlockEntity dédiée avec un champ `sculpture`, schéma **1** : +nom, identifiant du modèle, dimensions et couples position/couleur ARGB des +voxels. La position est bornée à 16³, la couleur opaque, les doublons refusés. +L’objet transporte ces données dans son composant natif de bloc ; pose, butin, +repose et sauvegarde utilisent le cycle Minecraft. Le bloc réplique les données +aux clients qui chargent son chunk. Aucun joueur n’a besoin du fichier source +pour voir une statue déjà fabriquée. Un schéma inconnu ou invalide chargé dans +un bloc est préservé sans être interprété ni édité. + +L’atelier utilise un menu natif temporaire, sans stock caché persistant. +Le serveur vérifie le menu actif, son jeton, la proximité, le bloc, les droits +et le veto d’utilisation Fabric. La sélection envoie seulement des voxels bornés +(un paquet atomique de moins de 32 Kio), jamais un chemin ou un fichier exécutable. +La teinte vient du véritable objet d’argile côté serveur et chaque retrait du +résultat consomme une unité. La fermeture ou la destruction de l’atelier annule +le droit de produire ; le travail local tardif ne change pas un autre menu. + +## Contrat d’import et limites + +GLB **2.0 autonome**, scène par défaut (ou première scène), triangles indexés +ou non, transformations hiérarchiques matrice ou TRS, matériaux de couleur +et textures PNG/JPEG intégrées, UV avec répétition/clamp/miroir. Le matériau +opaque ou à découpe alpha est voxelisé en surface. Le calcul s’effectue sur +un fil de travail ; les faces visibles du résultat sont mises en cache pour +le rendu. L’extension de matériau non éclairé peut être lue pour sa couleur. + +Limites explicites : fichier 8 Mio, JSON 1 Mio, hiérarchie de 64 niveaux, +16 384 triangles, 4 194 304 pixels décodés et autant de pixels de matériaux. +Les plans gardent les plafonds historiques de cellules et de volume. +Les grands scans demandent donc souvent une simplification préalable. + +Squelettes et morphing doivent être figés avant export. Les animations ne sont +pas jouées (la pose statique des nœuds est utilisée). Couleurs de sommets, +accessors sparse, primitives non triangulaires, transparence mélangée, +compressions et extensions de textures non gérées sont refusés explicitement. +Les couleurs de sommets doivent être converties en texture. Aucun téléchargement +ni référence externe du GLB n’est résolu. OBJ, FBX, STL, VOX et glTF multifichier +ne font pas partie de ce premier import. Un GLB sans surface visible est refusé. + +Une miniature représente au maximum 4096 voxels de 1/16 de bloc, centrés en X/Z, +posés au sol. Les petits détails peuvent disparaître. Sa collision est le cube +contenant la sculpture ; les voxels ne sont pas des blocs exploitables ou des +inventaires. Les argiles utilisent la palette de couleur publiée du mod, avec +éclairage du rendu, sans texture de matière distincte sur chaque voxel. + +Aucune dépendance tierce supplémentaire : Minecraft **26.3**, Fabric et Java 25 +restent ceux du dépôt. Lecture suivant la [spécification Khronos glTF 2.0](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html). + +## Vérifications + +`Statuary106ClientChecks` passe sur un client natif Minecraft 26.3 avec serveur +intégré, dans un nouveau monde plat jetable, graine 106. Il vérifie : + +- Lecture d’un GLB texturé avec transformation de scène, conservation des + proportions et des couleurs ; refus des fichiers hors limites, références + externes, offsets invalides et hiérarchies cycliques. +- Import indépendant de [BoxTextured de Khronos](https://github.com/KhronosGroup/glTF-Sample-Assets/tree/main/Models/BoxTextured), + triangles indexés et texture embarquée. Le fichier n’est pas distribué avec le mod. +- Dossier partagé avec filtres distincts, rotation, identité du modèle et + recoloration par les seize argiles. +- Fabrication native en survie : absence de résultat sans argile, coût d’une + unité, restitution du reliquat à la fermeture et production par Maj-clic. +- Pose par clic natif dans la portée Sanctuary du joueur, butin complet, + repose, rendu dans le monde, dans l’inventaire et dans l’aperçu. +- Même source convertie au Métabli en plan de 32 blocs de hauteur, sans + modifier une miniature existante ; annulation d’un import à la fermeture. +- Sauvegarde, suppression du GLB, réouverture du monde : modèle transmis au + nouveau client et sculptures colorées conservées dans l’inventaire. +- Captures FR/EN relues dans `build/statuary106-evidence/`. + +Commande de reproduction : + +```sh +./gradlew :sanctuary:runClientGameTest \ + -PsanctuaryClientTests=true -PsanctuaryStatuary106ClientTests=true \ + -PsanctuaryClientNoVsync=true -PsanctuaryQuickTests=true +``` + +Le contrôle de régression `Plans072ClientChecks` passe également sur la copie +livrée en **47 s** : **88 modèles natifs, aucun refus**, textures UV, palette +active, statues de 32 blocs, rotations, matériaux, export gzip, aperçu, confirmation +et annulation en créatif, constructions, refus d’usurpation et pose payée en +survie. Commande identique avec `-PsanctuaryPlans072ClientTests=true` à la place +de `-PsanctuaryStatuary106ClientTests=true`. Journal : +`build/statuary106-plans-regression.log`. + +Les premiers échecs ont permis de corriger le champ natif `recipes` du critère +de découverte de recette. Les attentes du test ont ensuite été ajustées pour +attendre la confirmation serveur des clics et respecter la portée de pose du +nouveau joueur. Aucun contournement de ces règles n’est ajouté au mod. + +## Livraison locale + +La copie `build/release-beta106` part de la livraison beta.105 et ajoute les +30 fichiers de production concernés par ce ticket. Les sources beta.107 des +œufs, arrivées en parallèle dans le dossier partagé, restent conservées dans +leur propre livraison. `build/statuary106-source-manifest.json` décrit la copie. +La commande suivante a réussi sur cette copie en **5 min**, **132 tâches** : + +```sh +./gradlew check build assemblePack assembleTestPack :sanctuary:runClientGameTest \ + -x :sanctuary:runGameTest \ + -PsanctuaryClientTests=true -PsanctuaryStatuary106ClientTests=true \ + -PsanctuaryClientNoVsync=true -PsanctuaryQuickTests=true +``` + +Le GameTest dédié reste exclu conformément au refus antérieur d’accepter son +EULA. Le serveur intégré a vérifié les écritures, le coût, le butin, la sauvegarde +et la transmission au client. Les essais ont tourné sur macOS ; une connexion +depuis deux ordinateurs et les autres pilotes graphiques n’ont pas été testés. + +Les sources JAR correspondent exactement aux sources Java de la copie isolée. +Les deux archives contiennent le même JAR Sanctuary, avec métadonnées beta.106 +et Minecraft 26.3. Aucune classe de test ni modèle GLB de test n’est distribué. +Rapport et empreintes : `build/statuary106-artifact.json`. + +- [Pack normal](../build/Sanctuary-beta.106.mrpack), 10 144 169 octets. + SHA-256 : `b8b76ba914c8ceaed1b1d25f0817bd4cdeb7a4bd5ad43222c6970162811ff901`. +- [Pack de test](../build/Sanctuary-Test-beta.106.mrpack), 10 163 092 octets. + SHA-256 : `e55d5871f1e31d3ed46de32d748eba51be2029c60462cf0155be90b8dd40118b`. + +Aucun tag publié, canal packwiz avancé ou déploiement dans Prism. diff --git a/docs/temple-crash-beta097.md b/docs/temple-crash-beta097.md new file mode 100644 index 0000000..c593545 --- /dev/null +++ b/docs/temple-crash-beta097.md @@ -0,0 +1,102 @@ +# beta.097 — crash de préparation des temples + +Ticket local EXP-03, branche `codex/expedition-temple-crash-beta097`. +Rapport du 16 septembre 2026 à 13:12:19, Sanctuary beta.089, Minecraft +26.3-pre-2 / Windows. Graine du monde : `-4700804240597771092` ; graine +locale de Kai/Efe : `7023521385479800534`. + +Le chargement s'arrête dans `ExpansionStructures23.ancientTemples002` : aucun +site accepté pour `minecraft:desert_pyramid` après 1 926 colonnes. La recherche +historique couvre seulement 72 % du rayon nominal de l'île. Le défaut existe +encore dans beta.096 sur Minecraft 26.3 finale. + +## Contrat avant correction + +Les identifiants, biomes, graines, centres des îles et journaux restent inchangés. +Le relief reste identique, sauf l’assise locale explicitement autorisée pour un +temple de secours lorsqu’aucun sol naturel ne convient. Aucun monde personnel n'est ouvert/modifié, +aucun chunk joué n'est régénéré, aucune expansion n'est activée. + +Conserver la recherche historique et son ordre exact en premier : les sites +admissibles déjà trouvés ne doivent pas changer. Étudier une recherche de +secours finie dans les autres chunks de l'île uniquement après l'échec de cette +recherche, avec les mêmes règles natives de biome, de pente, de fondation et +de protection. Après l’échec réel de cette recherche sur la graine du rapport, le créateur a +explicitement demandé de **forcer la présence du temple**. Un dernier recours +classe les sites intérieurs par pente et utilise les pièces vanilla, même si +le biome exact n’est pas celui requis pour leur apparition spontanée. La +fondation naturelle est testée en premier ; sinon une assise locale est +préparée sous le bâtiment. Aucun temple manquant n’est déclaré présent. Le budget de calcul +reste borné. Les cas auparavant réussis doivent conserver leurs coordonnées. + +La vérification utilise exclusivement de nouveaux mondes de développement et +leur réouverture, avec le client et son serveur intégré. Aucun serveur dédié +ni nouvelle acceptation d'EULA. + +La recherche naturelle élargie échoue également sur la graine du rapport +(440 candidats supplémentaires, 2 366 colonnes). Le dernier recours trouve une +pyramide au chunk `[-4, 43]`, sur sol naturel, sans assise ajoutée pour cette +reproduction. Le temple de jungle reste au chunk `[43, 2]`. + +La recherche historique garde 32 768 colonnes au maximum, portée à 65 536 pour +la recherche élargie. Le dernier recours utilise un échantillonneur distinct +borné à 65 536 colonnes, classe les candidats intérieurs par pente, et examine +au plus 32 bâtiments complets. La variante au sol naturel est privilégiée ; +l’assise, si nécessaire, garde les vérifications d’emprise, de hauteur et de +protection. Les contraintes naturelles exactes de biome/pente ne peuvent alors +plus annuler à elles seules l’existence du temple demandé. + +## Contrat du complément NBT + +Le champ entier optionnel `sanctuary_native.temple_foundation_min_y` accompagne +seulement les nouveaux starts exigeant une fondation. Son absence conserve le +comportement antérieur ; aucun champ n’est ajouté aux starts ordinaires. La +fondation en grès ou pierre moussue est produite pendant la décoration native, +uniquement dans le chunk en cours, avant la construction vanilla des salles, +coffres et pièges. Elle ne s’exécute jamais au chargement d’un chunk déjà généré. +Le cache natif conserve ce complément via la sérialisation existante des starts. +Aucun journal ni chunk historique n’est migré. Les protections spatiales restent +actives, et l’assise est incluse dans la réservation du temple. + +## Aperçu des plans (demande complémentaire) + +Le créateur demande aussi d’identifier les matériaux directement dans l’aperçu. +Les modèles et UV du pack actif remplacent les aplats ; états/orientations, +formes non cubiques et couleurs de biome sont conservés. Les conflits gardent +un contour rouge, les blocs déjà posés disparaissent de l’aperçu. Le nom du bloc +visé est affiché. Rendu seulement : aucun bloc n’est posé par cet affichage. + +## Vérifications beta.097 + +- Avant correction, reproduction sur 26.3 finale du même échec que beta.089 : + graine locale `7023521385479800534`, exactement 1 926 colonnes échantillonnées. +- `Temple097ClientChecks` : création réelle avec la graine du rapport, puis + réouverture du monde de développement et restauration du cache. Deux temples + natifs complets : pyramide **4 coffres et 9 TNT**, jungle **2 coffres et + 2 distributeurs**. Starts NBT, pièges, blocs et localisation identiques après + reprise. Réussi en 3 min 30 s. Le premier essai corrigé avait déjà chargé le + monde, mais son pilote attendait au Hello World ; l’automatisation de cette + étape a ensuite été ajoutée au test. +- Régression du Métabli avec les nouveaux modèles : sélection, K, génération, + import/export, pose payée, rotation, masquage, interfaces FR/EN et huit + matériaux dont coffre, escalier, four et feuillage. Les contrôles de fondation + grès/pierre moussue vérifient aussi le complément NBT, la continuité des + écritures, leur découpage strict par chunk et l’absence d’écriture sur un + ancien start non marqué. La fondation est vérifiée via un WorldGenLevel de + test ; la graine réelle n’en a pas besoin. +- Captures inspectées dans `build/preview097-evidence/`. Les modèles ordinaires + sont translucides, les modèles spéciaux gardent leur rendu natif. La sélection + graphique est bornée aux 4 096 blocs les plus proches dans le champ de vision, + à moins de 96 blocs ; les matériaux/comptages concernent toujours le plan entier. + +Les journaux sont `build/temple097-repro.log`, `build/temple097-fallback.log`, +`build/temple097-client.log`, `build/preview097-client.log` et +`build/beta097-check.log`. Le reçu `build/beta097-artifact.json` vérifie les +archives, les sources embarquées et la conservation des textures existantes. +Aucun essai Windows ni shader tiers n’est revendiqué. Aucun monde personnel ni +instance Prism n’a été ouvert ou mis à jour ; le canal packwiz reste inchangé. + +Validation finale : `./gradlew check build assemblePack assembleTestPack +-x :sanctuary:runGameTest` réussit en **2 min 13 s**, 124 tâches. La suite native +Métabli/aperçu/fondations réussit en **59 s**. Archives normal et Test beta.097 +exportées et vérifiées ; les archives beta.096 restent inchangées. diff --git a/docs/tooltip-crash-beta093.md b/docs/tooltip-crash-beta093.md new file mode 100644 index 0000000..73c53d3 --- /dev/null +++ b/docs/tooltip-crash-beta093.md @@ -0,0 +1,85 @@ +# beta.093 — Recherche créative et infobulles des familiers + +Branche `codex/async-tooltip-crash-beta093`, Minecraft 26.3. + +## Diagnostic + +Le rapport fourni concerne Windows, Sanctuary beta.089 et Minecraft 26.3-pre-2. +Pendant la saisie d'un `w`, la recherche créative attend son index asynchrone. +Cet index construit les infobulles sur un worker (`SessionSearchTrees`), puis +`CompanionTooltips` appelle `Font.getSplitter().splitLines`. La mesure d'un glyphe +non encore chargé peut déclencher une écriture de texture, en concurrence avec +une passe de rendu : `Close the existing render pass before performing additional +commands`. L'exception du worker remonte ensuite par la saisie de caractères. + +La même mise en page était encore présente en beta.092 ; le correctif cible la +version actuelle 26.3 finale. Il ne nécessite aucun changement de pilote graphique. + +## Correction + +Les deux chemins de descriptions, catalogue actuel et pouvoirs historiques, +utilisent un même point de mise en page. Sur le fil client, il conserve la largeur +adaptée à la fenêtre et le découpage habituel. Sur un worker, il renvoie des +composants textuels complets avec leur couleur, sans accéder à la police ni à la +fenêtre. La recherche garde les noms et descriptions ; aucune ligne n'est cachée +pour contourner le problème. Aucun travail graphique n'est renvoyé au fil client +avec attente, ce qui éviterait mal le crash et risquerait de bloquer l'indexation. + +Aucune modification de sauvegarde, génération, règles de jeu, images ou registres. +Aucun déploiement dans une instance personnelle et aucun monde existant ouvert. + +## Vérifications + +Le parcours `Tooltip093ClientChecks` utilise un monde plat de développement neuf, +graine 42, et une sonde uniquement dans les GameTests. Cette sonde interdit tout +accès de mesure à la police depuis un worker, même lorsque ses glyphes sont déjà +en cache ; elle n'est pas embarquée dans le mod distribué. + +Avant le correctif, le test échoue dans `CompanionTooltips` avec +`TOOLTIP093_OFF_THREAD_FONT`, ce qui reproduit l'accès interdit identifié dans le +rapport sans dépendre d'une course aléatoire du pilote. Journal : +`build/tooltip093-before-client.log`. + +Après correction, le même parcours passe en **1 min 13 s** sur le client natif +Minecraft 26.3/macOS : + +- 88 œufs, français/anglais, descriptions cryptiques/détaillées : construction + sur worker sans aucune mesure de police. +- Comparaison du texte indexé avec le texte affiché : contenu complet conservé, + malgré les retours à la ligne réservés à l'affichage. +- `SessionSearchTrees.updateCreativeTooltips`, recherche native et reconstruction + de l'index après changement de langue. +- Rechargement effectif des ressources, puis saisie native de `w` et `olf` dans + la recherche créative. Capture anglaise inspectée : le champ contient `wolf` + et affiche l'œuf de loup et son armure. Captures conservées dans + `build/tooltip093-evidence/`. + +```sh +./gradlew :sanctuary:runClientGameTest -x :sanctuary:runGameTest \ + -PsanctuaryClientTests=true -PsanctuaryTooltip093ClientTests=true \ + -PsanctuaryQuickTests=true -PsanctuaryClientNoVsync=true +``` + +Journal : `build/tooltip093-client.log`, marqueur `TOOLTIP093_PASS`. + +## Livraison vérifiée + +`./gradlew check build assemblePack assembleTestPack -x :sanctuary:runGameTest` +passe en **2 min 36 s**, 124 tâches (`build/tooltip093-check.log`). +Les exports, JAR et sources correspondent. Seule la classe de production +`CompanionTooltips` diffère de beta.092 ; aucune sonde GameTest n'est distribuée. +Images, données, JEI et anciennes archives restent identiques. +Reçu local : `build/tooltip093-artifact.json`. + +- [Pack normal](../build/Sanctuary-beta.093.mrpack), SHA-256 + `35a1582c044955579a7120997864ff2080e276e81180ab9982397241ff4c3a1d`. +- [Pack Test](../build/Sanctuary-Test-beta.093.mrpack), SHA-256 + `48c9abe733c2993a752413a34852223d35ad33e99d4f7e1d4f284969824dc275`. + +## Limites + +Le rapport initial est Windows/pre-2 ; les essais du correctif sont macOS/26.3 +finale, avec serveur intégré de développement. L'accès fautif est reproduit +avec une sonde déterministe, sans revendiquer la reproduction exacte de la +course du pilote AMD. Serveur dédié exclu et aucune EULA acceptée. Les archives +locales ne mettent pas à jour une installation personnelle ni le canal packwiz. diff --git a/gradle.properties b/gradle.properties index 023ce43..eef81f7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -9,8 +9,8 @@ loom_version=1.17.20 fabric_api_version=0.160.5+26.3 # Release counter: beta.001, .002, .003, ... (see docs/versioning.md). -mod_version=beta.092 -pack_version=beta.092 +mod_version=beta.110 +pack_version=beta.110 resource_pack_version=beta.090 maven_group=fr.koka.sanctuary jei_version=30.32.0-sanctuary.3 diff --git a/mods/sanctuary/build.gradle b/mods/sanctuary/build.gradle index f7ba1ab..7962dd4 100644 --- a/mods/sanctuary/build.gradle +++ b/mods/sanctuary/build.gradle @@ -93,6 +93,10 @@ tasks.named('runGameTest') { } tasks.named('processGametestResources') { + def statuary106Client = providers.gradleProperty('sanctuaryStatuary106ClientTests').map { it.toBoolean() }.getOrElse(false) + inputs.property('sanctuaryStatuary106ClientTests', statuary106Client) + if (statuary106Client) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.client.Statuary106ClientChecks') } + def villageOnly = providers.gradleProperty('sanctuaryVillageOnly').map { it.toBoolean() }.getOrElse(false) def focused = providers.gradleProperty('sanctuaryFocusedTests').getOrElse('').split(',').findAll { !it.isEmpty() } def focusClasses = [plans072: 'Plans072GameTests', starter052: 'Starter052GameTests', dispenser051: 'Dispenser051GameTests', head050: 'Head050GameTests', boatshead: 'BoatsHead049GameTests', poultry: 'Poultry048GameTests', starinspect: 'StarInspection047GameTests', skyflight: 'SkyFlight046GameTests', eggaudit: 'Companion044GameTests', headblocks: 'HeadBlocks043GameTests', operator: 'OperatorStars040GameTests', starter: 'Starter039GameTests', gravelayout: 'GraveInventory039GameTests', foodknowledge: 'FoodKnowledge039GameTests', graves: 'GravesFood038GameTests', realtime: 'Realtime037GameTests', corrections: 'Corrections036GameTests', collections: 'Collections035GameTests', notifications: 'Notifications034GameTests', menus: 'Menus033GameTests', aerial: 'Aerial24WorldGameTests', transit: 'Transit29WorldGameTests', village: 'Village306GameTests', drained: 'Drained307GameTests', expeditions: 'AncientExpeditions001GameTests', progression: 'Progression003GameTests', map: 'Atlas005GameTests', demeure: 'Demeure006GameTests', mining: 'Mining008GameTests', building: 'Building009GameTests', inventory: 'Inventory010GameTests', cycle: 'Cycle011GameTests', inventoryflow: 'Inventory012GameTests', sorting: 'Inventory014GameTests', tutorial: 'Tutorial016GameTests', movement: 'Movement017GameTests', accessories: 'Accessory018GameTests', companions: 'Companion019GameTests', familiar: 'Familiar020GameTests', carry: 'Carry027GameTests', familiarhit: 'Familiar029GameTests', refonte: 'Companion032GameTests', lights: 'Dynamic030GameTests', cosmetics: 'HeadCosmetic021GameTests', startup: 'StartupPerformanceGameTests', recipes: 'Recipes024GameTests'] @@ -123,6 +127,39 @@ tasks.named('processGametestResources') { def welcome083Client = providers.gradleProperty('sanctuaryWelcome083ClientTests').map { it.toBoolean() }.getOrElse(false) inputs.property('sanctuaryWelcome083ClientTests', welcome083Client) if (welcome083Client) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.client.Welcome083ClientChecks') } + def melt109Client = providers.gradleProperty('sanctuaryMelt109ClientTests').map { it.toBoolean() }.getOrElse(false) + inputs.property('sanctuaryMelt109ClientTests', melt109Client) + if (melt109Client) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.client.Melt109ClientChecks') } + def snow108Client = providers.gradleProperty('sanctuarySnow108ClientTests').map { it.toBoolean() }.getOrElse(false) + inputs.property('sanctuarySnow108ClientTests', snow108Client) + if (snow108Client) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.client.Snow108ClientChecks') } + def eggs107Client = providers.gradleProperty('sanctuaryEggs107ClientTests').map { it.toBoolean() }.getOrElse(false) + inputs.property('sanctuaryEggs107ClientTests', eggs107Client) + if (eggs107Client) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.client.Eggs107ClientChecks') } + def living105Client = providers.gradleProperty('sanctuaryLiving105ClientTests').map { it.toBoolean() }.getOrElse(false) + inputs.property('sanctuaryLiving105ClientTests', living105Client) + if (living105Client) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.client.Living105ClientChecks') } + def bricks102Client = providers.gradleProperty('sanctuaryBricks102ClientTests').map { it.toBoolean() }.getOrElse(false) + inputs.property('sanctuaryBricks102ClientTests', bricks102Client) + if (bricks102Client) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.client.Bricks102ClientChecks') } + def landing101Client = providers.gradleProperty('sanctuaryLanding101ClientTests').map { it.toBoolean() }.getOrElse(false) + inputs.property('sanctuaryLanding101ClientTests', landing101Client) + if (landing101Client) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.client.Landing101ClientChecks') } + def schematics100Client = providers.gradleProperty('sanctuarySchematics100ClientTests').map { it.toBoolean() }.getOrElse(false) + inputs.property('sanctuarySchematics100ClientTests', schematics100Client) + if (schematics100Client) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.client.Schematics100ClientChecks') } + def exploration100Client = providers.gradleProperty('sanctuaryExploration100ClientTests').map { it.toBoolean() }.getOrElse(false) + inputs.property('sanctuaryExploration100ClientTests', exploration100Client) + if (exploration100Client) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.client.Exploration100ClientChecks') } + def catalogue099Client = providers.gradleProperty('sanctuaryCatalogue099ClientTests').map { it.toBoolean() }.getOrElse(false) + inputs.property('sanctuaryCatalogue099ClientTests', catalogue099Client) + if (catalogue099Client) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.client.Catalogue099ClientChecks') } + def temple097Client = providers.gradleProperty('sanctuaryTemple097ClientTests').map { it.toBoolean() }.getOrElse(false) + inputs.property('sanctuaryTemple097ClientTests', temple097Client) + if (temple097Client) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.client.Temple097ClientChecks') } + def metatable096Client = providers.gradleProperty('sanctuaryMetatable096ClientTests').map { it.toBoolean() }.getOrElse(false) + inputs.property('sanctuaryMetatable096ClientTests', metatable096Client) + if (metatable096Client) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.client.Metatable096ClientChecks') } def piston087Client = providers.gradleProperty('sanctuaryPiston087ClientTests').map { it.toBoolean() }.getOrElse(false) inputs.property('sanctuaryPiston087ClientTests', piston087Client) if (piston087Client) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.client.Piston087ClientChecks') } @@ -132,6 +169,9 @@ tasks.named('processGametestResources') { def fourneau085Client = providers.gradleProperty('sanctuaryFourneau085ClientTests').map { it.toBoolean() }.getOrElse(false) inputs.property('sanctuaryFourneau085ClientTests', fourneau085Client) if (fourneau085Client) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.client.Fourneau085ClientChecks') } + def tooltip093Client = providers.gradleProperty('sanctuaryTooltip093ClientTests').map { it.toBoolean() }.getOrElse(false) + inputs.property('sanctuaryTooltip093ClientTests', tooltip093Client) + if (tooltip093Client) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.gametest.Tooltip093ClientChecks') } def pick092Client = providers.gradleProperty('sanctuaryPick092ClientTests').map { it.toBoolean() }.getOrElse(false) inputs.property('sanctuaryPick092ClientTests', pick092Client) if (pick092Client) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.gametest.Pick092ClientChecks') } @@ -1077,3 +1117,11 @@ tasks.register('atlas059Smoke', JavaExec) { mainClass = 'fr.koka.sanctuary.atlas.AtlasMarker059Smoke' } tasks.named('check') { dependsOn('atlas059Smoke') } + +tasks.register('seasonal105Smoke', JavaExec) { + group = 'verification' + dependsOn('testClasses') + classpath = sourceSets.test.runtimeClasspath + mainClass = 'fr.koka.sanctuary.weather.Seasonal105Smoke' +} +tasks.named('check') { dependsOn('seasonal105Smoke') } diff --git a/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/Bricks102ClientChecks.java b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/Bricks102ClientChecks.java new file mode 100644 index 0000000..9f00bd3 --- /dev/null +++ b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/Bricks102ClientChecks.java @@ -0,0 +1,103 @@ +package fr.koka.sanctuary.client; +import com.mojang.blaze3d.platform.InputConstants; +import fr.koka.sanctuary.SanctuaryMod; +import fr.koka.sanctuary.building.ColoredBricks; +import fr.koka.sanctuary.gametest.Starter039TestSupport; +import java.util.*; +import java.util.concurrent.atomic.*; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; +import net.fabricmc.fabric.api.client.gametest.v1.FabricClientGameTest; +import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.screens.worldselection.WorldCreationUiState; +import net.minecraft.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.BlockTags; +import net.minecraft.world.item.*; +import net.minecraft.world.item.crafting.*; +import net.minecraft.world.level.block.*; +import net.minecraft.world.level.block.state.properties.*; +import net.minecraft.world.item.enchantment.Enchantments; + +/** Native registry, recipes, loot, creative tabs and baked models in a fresh integrated world. */ +public final class Bricks102ClientChecks implements FabricClientGameTest { + public void runTest(ClientGameTestContext c){ + var intro=new AtomicBoolean(true); + c.runOnClient(m->{m.options.renderDistance().set(8);m.options.simulationDistance().set(8);m.options.pauseOnLostFocus=false;IntroClient.setQuickTestIntro(true);}); + ClientTickEvents.END_CLIENT_TICK.register(m->{if(intro.get()&&m.gui.screen() instanceof HelloWorldScreen screen){ + Starter039TestSupport.selectFirst(screen); + screen.children().stream().filter(Button.class::isInstance).map(Button.class::cast).filter(b->b.getClass().getSimpleName().equals("ColorButton")).findFirst().orElseThrow().onPress(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); + }}); + net.fabricmc.fabric.api.client.gametest.v1.world.TestWorldSave saved; + try(var world=c.worldBuilder().adjustSettings(s->{s.setName("Bricks102 "+UUID.randomUUID());s.setSeed("102");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 level=p.level();var pos=new BlockPos(0,3,0); + check(ColoredBricks.families().size()==16,"All colors registered");int index=0; + for(var f:ColoredBricks.families()){ + String color=f.color().getName(); + for(var block:List.of(f.bricks(),f.slab(),f.stairs(),f.clay()))check(block.asItem()!=Items.AIR,"Block item exists "+block); + check(f.bricks().defaultBlockState().is(BlockTags.MINEABLE_WITH_PICKAXE),"Pickaxe tag "+color); + check(f.clay().defaultBlockState().is(BlockTags.MINEABLE_WITH_SHOVEL),"Shovel tag "+color); + var shovel=new ItemStack(Items.DIAMOND_SHOVEL);var silk=shovel.copy();silk.enchant(level.registryAccess().lookupOrThrow(Registries.ENCHANTMENT).getOrThrow(Enchantments.SILK_TOUCH),1); + drop(Block.getDrops(f.clay().defaultBlockState(),level,pos,null,p,shovel),f.clayBall(),4); + drop(Block.getDrops(f.clay().defaultBlockState(),level,pos,null,p,silk),f.clay().asItem(),1); + drop(Block.getDrops(f.slab().defaultBlockState().setValue(SlabBlock.TYPE,SlabType.DOUBLE),level,pos,null),f.slab().asItem(),2); + drop(Block.getDrops(f.slab().defaultBlockState(),level,pos,null),f.slab().asItem(),1); + drop(Block.getDrops(f.bricks().defaultBlockState(),level,pos,null),f.bricks().asItem(),1); + drop(Block.getDrops(f.stairs().defaultBlockState(),level,pos,null),f.stairs().asItem(),1); + check(f.slab().defaultBlockState().setValue(SlabBlock.WATERLOGGED,true).getFluidState().isSource(),"Slab water "+color); + check(f.stairs().defaultBlockState().setValue(StairBlock.WATERLOGGED,true).getFluidState().isSource(),"Stair water "+color); + for(String suffix:List.of("clay_ball_from_dye","clay_from_dye","brick_from_dye","bricks_from_dye","clay","bricks","brick_slab","brick_stairs","brick_slab_from_stonecutting","brick_stairs_from_stonecutting","clay_ball_smelting","clay_smelting"))check(s.getRecipeManager().byKey(ResourceKey.create(Registries.RECIPE,SanctuaryMod.id(color+"_"+suffix))).isPresent(),"Recipe loaded "+color+"_"+suffix); + craft(s,level,2,2,List.of(f.brick(),f.brick(),f.brick(),f.brick()),f.bricks().asItem(),1); + craft(s,level,2,2,List.of(f.clayBall(),f.clayBall(),f.clayBall(),f.clayBall()),f.clay().asItem(),1); + craft(s,level,3,1,List.of(f.bricks().asItem(),f.bricks().asItem(),f.bricks().asItem()),f.slab().asItem(),6); + craft(s,level,3,3,List.of(f.bricks().asItem(),Items.AIR,Items.AIR,f.bricks().asItem(),f.bricks().asItem(),Items.AIR,f.bricks().asItem(),f.bricks().asItem(),f.bricks().asItem()),f.stairs().asItem(),4); + var dye=BuiltInRegistries.ITEM.getValue(net.minecraft.resources.Identifier.withDefaultNamespace(color+"_dye")); + for(var pair:List.of(new Item[]{Items.CLAY_BALL,f.clayBall()},new Item[]{Items.CLAY,f.clay().asItem()},new Item[]{Items.BRICK,f.brick()},new Item[]{Items.BRICKS,f.bricks().asItem()}))craft(s,level,3,3,List.of(pair[0],pair[0],pair[0],pair[0],dye,pair[0],pair[0],pair[0],pair[0]),pair[1],8); + var input=new SingleRecipeInput(new ItemStack(f.clayBall()));var cooked=s.getRecipeManager().getRecipeFor(RecipeType.SMELTING,input,level).orElseThrow().value().assemble(input);check(cooked.is(f.brick()),"Smelt brick "+color); + input=new SingleRecipeInput(new ItemStack(f.clay()));cooked=s.getRecipeManager().getRecipeFor(RecipeType.SMELTING,input,level).orElseThrow().value().assemble(input);check(cooked.is(BuiltInRegistries.ITEM.getValue(net.minecraft.resources.Identifier.withDefaultNamespace(color+"_terracotta"))),"Smelt terracotta "+color); + var stoneInput=new SingleRecipeInput(new ItemStack(f.bricks())); + for(var variant:List.of(new Object[]{"brick_slab",f.slab().asItem(),2},new Object[]{"brick_stairs",f.stairs().asItem(),1})){ + var recipe=(StonecutterRecipe)s.getRecipeManager().byKey(ResourceKey.create(Registries.RECIPE,SanctuaryMod.id(color+"_"+variant[0]+"_from_stonecutting"))).orElseThrow().value();check(recipe.matches(stoneInput,level),"Stonecutting accepts input");var cut=recipe.assemble(stoneInput);check(cut.is((Item)variant[1])&&cut.getCount()==(int)variant[2],"Stonecutting result"); + } + int x=(index%8)*3,z=(index/8)*5;level.setBlockAndUpdate(new BlockPos(x,1,z),f.bricks().defaultBlockState());level.setBlockAndUpdate(new BlockPos(x,2,z),f.slab().defaultBlockState().setValue(SlabBlock.TYPE,SlabType.TOP));level.setBlockAndUpdate(new BlockPos(x+1,1,z),f.stairs().defaultBlockState().setValue(StairBlock.FACING,Direction.SOUTH));level.setBlockAndUpdate(new BlockPos(x,1,z+1),f.clay().defaultBlockState());index++; + } + fr.koka.sanctuary.recipes.RecipeService.discover(p,Items.CLAY_BALL);fr.koka.sanctuary.recipes.RecipeService.refresh(p); + var known=fr.koka.sanctuary.recipes.RecipeService.knowledge(p); + for(var f:ColoredBricks.families())check(known.recipes.contains("sanctuary:"+f.color().getName()+"_brick_stairs_from_stonecutting"),"Collection unlock "+f.color()); + s.getCommands().performPrefixedCommand(s.createCommandSourceStack(),"sanctuary time vanilla");s.getCommands().performPrefixedCommand(s.createCommandSourceStack(),"time set noon"); + p.teleportTo(11,8,19);p.setYRot(180);p.setXRot(26);p.getAbilities().flying=true;p.onUpdateAbilities(); + }); + c.runOnClient(m->{ + CreativeModeTabs.tryRebuildTabContents(m.level.enabledFeatures(),true,m.level.registryAccess()); + var blocks=BuiltInRegistries.CREATIVE_MODE_TAB.getOrThrow(CreativeModeTabs.COLORED_BLOCKS).value().getDisplayItems(); + var ingredients=BuiltInRegistries.CREATIVE_MODE_TAB.getOrThrow(CreativeModeTabs.INGREDIENTS).value().getDisplayItems(); + for(var f:ColoredBricks.families()){ + for(var b:List.of(f.bricks(),f.slab(),f.stairs(),f.clay())){ + check(blocks.stream().anyMatch(i->i.is(b.asItem())),"Creative color tab "+b); + for(var state:b.getStateDefinition().getPossibleStates()){ + var parts=new ArrayList();m.getModelManager().getBlockStateModelSet().get(state).collectParts(net.minecraft.util.RandomSource.create(0),parts);check(!parts.isEmpty(),"Baked model "+state); + for(var part:parts)for(var direction:new Direction[]{Direction.UP,Direction.DOWN,Direction.NORTH,Direction.SOUTH,Direction.EAST,Direction.WEST,null})for(var quad:part.getQuads(direction))check(!quad.materialInfo().sprite().contents().name().toString().contains("missingno"),"No missing texture "+state); + } + } + for(var item:List.of(f.clayBall(),f.brick()))check(ingredients.stream().anyMatch(i->i.is(item)),"Creative ingredients "+item); + } + }); + c.waitTicks(20);c.runOnClient(m->{m.player.setYRot(180);m.player.setXRot(26);});c.waitTicks(4);c.takeScreenshot("bricks102-gallery");saved=world.getWorldSave();System.out.println("BRICKS102_NATIVE_PASS colors=16 recipes=192 blocks=64 items=96"); + } + try(var world=saved.open()){ + c.waitFor(m->m.player!=null&&m.gui.screen()==null,1200); + world.getServer().runOnServer(s->{var level=s.getPlayerList().getPlayers().getFirst().level();int i=0;for(var f:ColoredBricks.families()){var pos=new BlockPos((i%8)*3,1,(i/8)*5);check(level.getBlockState(pos).is(f.bricks()),"Saved bricks "+f.color());check(level.getBlockState(pos.above()).getValue(SlabBlock.TYPE)==SlabType.TOP,"Saved slab state");i++;}}); + } + System.out.println("BRICKS102_PASS"); + } + private static void craft(net.minecraft.server.MinecraftServer s,net.minecraft.server.level.ServerLevel level,int w,int h,List items,Item result,int count){var input=CraftingInput.of(w,h,items.stream().map(ItemStack::new).toList());var stack=s.getRecipeManager().getRecipeFor(RecipeType.CRAFTING,input,level).orElseThrow().value().assemble(input);check(stack.is(result)&&stack.getCount()==count,"Craft "+result);} + private static void drop(List drops,Item item,int count){check(drops.size()==1&&drops.getFirst().is(item)&&drops.getFirst().getCount()==count,"Loot "+item+" expected "+count+" got "+drops);} + private static KeyEvent enter(){return new KeyEvent(InputConstants.KEY_RETURN,0,0);} + private static void check(boolean ok,String message){if(!ok)throw new AssertionError(message);} +} diff --git a/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/Catalogue099ClientChecks.java b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/Catalogue099ClientChecks.java new file mode 100644 index 0000000..5839f52 --- /dev/null +++ b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/Catalogue099ClientChecks.java @@ -0,0 +1,19 @@ +package fr.koka.sanctuary.client; + +import net.fabricmc.fabric.api.client.gametest.v1.FabricClientGameTest; +import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext; + +/** End-to-end builds, personal history, shared catalogue, inline advancements and tap/hold K. */ +public final class Catalogue099ClientChecks implements FabricClientGameTest { + @Override public void runTest(ClientGameTestContext c){ + new Plans072ClientChecks().runTest(c); + c.runOnClient(m->{if(PlanHistory.entries().size()!=3)throw new AssertionError("Three genuinely completed plans retained");}); + new Metatable096ClientChecks().runTest(c); + try{ + var root=net.minecraft.nbt.NbtIo.readCompressed(PlanClient.directory().resolve("recent-completed.nbt"),net.minecraft.nbt.NbtAccounter.create(fr.koka.sanctuary.plans.SchematicIO.MAX_NBT)); + var entries=root.getList("Completed").orElseThrow();if(entries.size()!=3)throw new AssertionError("Three completed plans persisted"); + for(var entry:entries)fr.koka.sanctuary.plans.SchematicIO.decode((net.minecraft.nbt.CompoundTag)entry,"History"); + }catch(Exception e){throw new AssertionError(e);} + System.out.println("CATALOGUE099_PASS: complete creative builds, three-entry persisted history, uniform catalogue, native textured thumbnails, all-block palette, tap/hold K, inline tree and tracking, GUI scales 2/3, FR/EN"); + } +} diff --git a/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/Eggs107ClientChecks.java b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/Eggs107ClientChecks.java new file mode 100644 index 0000000..0d60ef1 --- /dev/null +++ b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/Eggs107ClientChecks.java @@ -0,0 +1,134 @@ +package fr.koka.sanctuary.client; + +import com.mojang.blaze3d.platform.InputConstants; +import fr.koka.sanctuary.companion.CompanionType; +import fr.koka.sanctuary.eggs.BirthEggs; +import fr.koka.sanctuary.gametest.Starter039TestSupport; +import fr.koka.sanctuary.progression.SanctuaryGameplay; +import java.util.*; +import java.util.concurrent.atomic.AtomicBoolean; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; +import net.fabricmc.fabric.api.client.gametest.v1.*; +import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext; +import net.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.component.DataComponents; +import net.minecraft.core.dispenser.BlockSource; +import net.minecraft.core.registries.*; +import net.minecraft.nbt.NbtOps; +import net.minecraft.network.chat.contents.TranslatableContents; +import net.minecraft.resources.Identifier; +import net.minecraft.server.level.*; +import net.minecraft.world.*; +import net.minecraft.world.entity.*; +import net.minecraft.world.entity.animal.Animal; +import net.minecraft.world.entity.animal.sheep.Sheep; +import net.minecraft.world.entity.item.ItemEntity; +import net.minecraft.world.item.*; +import net.minecraft.world.item.context.UseOnContext; +import net.minecraft.world.item.enchantment.Enchantments; +import net.minecraft.world.level.GameType; +import net.minecraft.world.level.block.*; +import net.minecraft.world.level.block.entity.*; +import net.minecraft.world.phys.*; + +public final class Eggs107ClientChecks implements FabricClientGameTest { + private static final AABB AREA = new AABB(-20,70,-20,20,100,20); + public void runTest(ClientGameTestContext c) { + var intro = new AtomicBoolean(true); + c.runOnClient(m->{m.options.renderDistance().set(6);m.options.simulationDistance().set(5);m.options.pauseOnLostFocus=false;IntroClient.setQuickTestIntro(true);}); + ClientTickEvents.END_CLIENT_TICK.register(m->{if(intro.get()&&m.gui.screen() instanceof HelloWorldScreen screen){ + Starter039TestSupport.selectFirst(screen); + screen.children().stream().filter(Button.class::isInstance).map(Button.class::cast).filter(b->b.getClass().getSimpleName().equals("ColorButton")).findFirst().orElseThrow().onPress(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()); + }}); + try { + for(boolean enabled:new boolean[]{true,false}) try(var world=c.worldBuilder().adjustSettings(s->{ + s.setName("Eggs107 "+enabled+" "+UUID.randomUUID());s.setSeed("107");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()); + s.getGameRules().set(SanctuaryGameplay.ENABLED,enabled,null); + }).create()) { + c.waitFor(m->m.player!=null&&m.gui.screen()==null&&(!enabled||ProgressionClient.active()&&!IntroSpawnFlash.active()),1200); + world.getServer().runOnServer(server->{ + var p=server.getPlayerList().getPlayers().getFirst();var l=p.level(); + check(BirthEggs.active(l)==enabled,"Gameplay scope "+enabled); + for(int x=-15;x<=15;x++)for(int z=-15;z<=15;z++)l.setBlockAndUpdate(new BlockPos(x,79,z),Blocks.GRASS_BLOCK.defaultBlockState()); + p.teleportTo(0,80,-4);p.getInventory().clearContent();p.setGameMode(GameType.SURVIVAL); + if(enabled) { ordinaryBirths(l,p); specialBirths(l,p); spawners(l,p,true); } + else { vanillaBirth(l);spawners(l,p,false); } + }); + } + System.out.println("EGGS107_NATIVE_PASS: births, variants, crosses, ownership, hatch/use/dispenser, special births, spawners, vanilla scope"); + } finally { intro.set(false); } + } + private static void ordinaryBirths(ServerLevel l,ServerPlayer p) { + // Exercise every common Animal breeding path and native child constructor. + String[] breeders={"armadillo","axolotl","bee","camel","cat","chicken","cow","donkey","fox","goat","hoglin","horse","llama","mooshroom","nautilus","ocelot","panda","pig","rabbit","sheep","strider","wolf"}; + for(String id:breeders){ + clear(l);var type=BuiltInRegistries.ENTITY_TYPE.getValue(Identifier.withDefaultNamespace(id)); + var a=(Animal)spawn(l,type);var b=(Animal)spawn(l,type);a.setInLove(p);b.setInLove(p); + a.spawnChildFromBreeding(l,b); + ItemStack egg=oneEgg(l);check(SpawnEggItem.getType(egg)==type,"Native species "+id); + check(l.getEntitiesOfClass(Mob.class,AREA).size()==2,"No simultaneous live child "+id); + check(a.getAge()==6000&&b.getAge()==6000&&!a.isInLove()&&!b.isInLove(),"Cooldown/love "+id); + var payload=egg.get(DataComponents.ENTITY_DATA).copyTagWithoutId();check(payload.getIntOr("Age",0)<0,"Baby payload "+id); + check(!payload.contains("UUID")&&!payload.contains("Pos")&&!payload.contains("Brain"),"Portable data "+id); + } + clear(l);var a=(Animal)spawn(l,EntityTypes.HORSE);var b=(Animal)spawn(l,EntityTypes.DONKEY);a.spawnChildFromBreeding(l,b); + check(SpawnEggItem.getType(oneEgg(l))==EntityTypes.MULE,"Horse/donkey gives mule"); + clear(l);var red=(Sheep)spawn(l,EntityTypes.SHEEP);var blue=(Sheep)spawn(l,EntityTypes.SHEEP);red.setColor(DyeColor.RED);blue.setColor(DyeColor.BLUE);red.spawnChildFromBreeding(l,blue); + var egg=roundTrip(l,oneEgg(l));check(egg.get(DataComponents.LORE)!=null,"Birth tooltip persisted"); + var sheep=(Sheep)hatch(l,p,egg.copy(),EntityTypes.SHEEP);check(sheep.isBaby()&&sheep.getColor()==DyeColor.PURPLE,"Native colour inheritance and guaranteed baby"); + var used=egg.copy();var onAdult=SpawnEggItem.spawnOffspringFromSpawnEgg(p,red,EntityTypes.SHEEP,l,new Vec3(7,80,7),used).orElseThrow(); + check(((Sheep)onAdult).getColor()==DyeColor.PURPLE&&onAdult.isBaby()&&used.isEmpty(),"Use on adult preserves actual birth"); + var dispenserPos=new BlockPos(10,80,10);var state=Blocks.DISPENSER.defaultBlockState().setValue(DispenserBlock.FACING,Direction.WEST);l.setBlockAndUpdate(dispenserPos,state); + var dispenser=(DispenserBlockEntity)l.getBlockEntity(dispenserPos); + dispenser.setItem(0,egg.copy()); + try{var method=DispenserBlock.class.getDeclaredMethod("dispenseFrom",ServerLevel.class,net.minecraft.world.level.block.state.BlockState.class,BlockPos.class);method.setAccessible(true);method.invoke(Blocks.DISPENSER,l,state,dispenserPos);}catch(Exception e){throw new RuntimeException(e);} + check(dispenser.getItem(0).isEmpty(),"Dispenser spends egg"); + check(l.getEntitiesOfClass(Sheep.class,new AABB(8,79,9,10,82,12)).stream().anyMatch(s->s.isBaby()&&s.getColor()==DyeColor.PURPLE),"Dispenser baby/variant"); + clear(l);var wolf=(net.minecraft.world.entity.animal.wolf.Wolf)spawn(l,EntityTypes.WOLF);wolf.tame(p);var other=(Animal)spawn(l,EntityTypes.WOLF);wolf.spawnChildFromBreeding(l,other); + var puppy=(net.minecraft.world.entity.animal.wolf.Wolf)hatch(l,p,oneEgg(l).copy(),EntityTypes.WOLF);check(puppy.isBaby()&&puppy.isTame()&&puppy.isOwnedBy(p),"Tamed wolf inherits owner"); + for(var species:CompanionType.values())check(SpawnEggItem.byId(BuiltInRegistries.ENTITY_TYPE.getValue(Identifier.withDefaultNamespace(species.id()))).isPresent(),"Catalogue native egg "+species.id()); + } + private static void specialBirths(ServerLevel l,ServerPlayer p) { + clear(l);var frog=(Animal)spawn(l,EntityTypes.FROG);var mate=(Animal)spawn(l,EntityTypes.FROG);frog.spawnChildFromBreeding(l,mate); + check(SpawnEggItem.getType(oneEgg(l))==EntityTypes.TADPOLE,"Frog tadpole egg"); + check(!frog.getBrain().hasMemoryValue(net.minecraft.world.entity.ai.memory.MemoryModuleType.IS_PREGNANT),"No second frogspawn"); + clear(l);var sniffer=(Animal)spawn(l,EntityTypes.SNIFFER);sniffer.spawnChildFromBreeding(l,(Animal)spawn(l,EntityTypes.SNIFFER)); + check(SpawnEggItem.getType(oneEgg(l))==EntityTypes.SNIFFER,"Sniffer replaces native block egg");check(hatch(l,p,oneEgg(l).copy(),EntityTypes.SNIFFER).isBaby(),"Snifflet"); + clear(l);var turtle=(net.minecraft.world.entity.animal.turtle.Turtle)spawn(l,EntityTypes.TURTLE);var partner=(Animal)spawn(l,EntityTypes.TURTLE); + try {var cls=Class.forName("net.minecraft.world.entity.animal.turtle.Turtle$TurtleBreedGoal");var ctor=cls.getDeclaredConstructor(net.minecraft.world.entity.animal.turtle.Turtle.class,double.class);ctor.setAccessible(true);var goal=ctor.newInstance(turtle,1.0);var field=net.minecraft.world.entity.ai.goal.BreedGoal.class.getDeclaredField("partner");field.setAccessible(true);field.set(goal,partner);var breed=cls.getDeclaredMethod("breed");breed.setAccessible(true);breed.invoke(goal);}catch(Exception e){throw new RuntimeException(e);} + check(!turtle.hasEgg()&&turtle.getAge()==6000&&partner.getAge()==6000,"Turtle no double laying");check(SpawnEggItem.getType(oneEgg(l))==EntityTypes.TURTLE,"Turtle egg"); + clear(l);var allay=spawn(l,EntityTypes.ALLAY);invoke(allay,"duplicateAllay");var allayEgg=oneEgg(l);check(SpawnEggItem.getType(allayEgg)==EntityTypes.ALLAY,"Allay egg");check(allayEgg.get(DataComponents.ENTITY_DATA).copyTagWithoutId().getLongOr("DuplicationCooldown",0)==6000,"Allay cooldown stored");check(l.getEntitiesOfClass(Mob.class,AREA).size()==1,"No duplicate allay entity"); + clear(l);var villager=(net.minecraft.world.entity.npc.villager.Villager)spawn(l,EntityTypes.VILLAGER);var other=(net.minecraft.world.entity.npc.villager.Villager)spawn(l,EntityTypes.VILLAGER); + try{var cls=net.minecraft.world.entity.ai.behavior.VillagerMakeLove.class;var method=cls.getDeclaredMethod("breed",ServerLevel.class,villager.getClass(),villager.getClass());method.setAccessible(true);var result=(Optional)method.invoke(new net.minecraft.world.entity.ai.behavior.VillagerMakeLove(),l,villager,other);check(result.isEmpty(),"Caller releases reserved bed");}catch(Exception e){throw new RuntimeException(e);} + check(SpawnEggItem.getType(oneEgg(l))==EntityTypes.VILLAGER&&villager.getAge()==6000&&other.getAge()==6000,"Villager birth"); + check(hatch(l,p,oneEgg(l).copy(),EntityTypes.VILLAGER).isBaby(),"Baby villager"); + } + private static void spawners(ServerLevel l,ServerPlayer p,boolean enabled) { + for(String mode:List.of("normal","silk","fortune","hand","creative","blockDropsOff")) { + clear(l);p.setGameMode(mode.equals("creative")?GameType.CREATIVE:GameType.SURVIVAL);var tool=mode.equals("hand")?ItemStack.EMPTY:new ItemStack(Items.DIAMOND_PICKAXE); + if(mode.equals("silk")||mode.equals("fortune"))tool.enchant(l.registryAccess().lookupOrThrow(Registries.ENCHANTMENT).getOrThrow(mode.equals("silk")?Enchantments.SILK_TOUCH:Enchantments.FORTUNE),mode.equals("silk")?1:3); + p.setItemInHand(InteractionHand.MAIN_HAND,tool);var pos=new BlockPos(1,80,1);l.setBlockAndUpdate(pos,Blocks.SPAWNER.defaultBlockState()); + ((SpawnerBlockEntity)l.getBlockEntity(pos)).getSpawner().setEntityId(EntityTypes.BLAZE,l,l.getRandom(),pos); + var rule=net.minecraft.world.level.gamerules.GameRules.BLOCK_DROPS;l.getGameRules().set(rule,!mode.equals("blockDropsOff"),l.getServer()); + check(p.gameMode.destroyBlock(pos),"Actual spawner break "+mode); + int count=l.getEntitiesOfClass(ItemEntity.class,AREA).stream().filter(e->e.getItem().is(Items.BLAZE_SPAWN_EGG)).mapToInt(e->e.getItem().getCount()).sum(); + check(count==(enabled&&(mode.equals("normal")||mode.equals("fortune"))?1:0),"Spawner egg count "+mode+" enabled="+enabled+" count="+count); + l.getGameRules().set(rule,true,l.getServer()); + } + p.setGameMode(GameType.CREATIVE); + } + private static void vanillaBirth(ServerLevel l) {clear(l);var a=(Animal)spawn(l,EntityTypes.COW);a.spawnChildFromBreeding(l,(Animal)spawn(l,EntityTypes.COW));check(l.getEntitiesOfClass(Animal.class,AREA).size()==3&&l.getEntitiesOfClass(ItemEntity.class,AREA).isEmpty(),"Vanilla birth unchanged");} + private static ItemStack roundTrip(ServerLevel l,ItemStack egg){var ops=l.registryAccess().createSerializationContext(NbtOps.INSTANCE);return ItemStack.CODEC.parse(ops,ItemStack.CODEC.encodeStart(ops,egg).getOrThrow()).getOrThrow();} + private static Mob hatch(ServerLevel l,ServerPlayer p,ItemStack egg,EntityType type){var before=new HashSet();for(var e:l.getEntitiesOfClass(Mob.class,AREA))before.add(e.getUUID());p.setItemInHand(InteractionHand.MAIN_HAND,egg);var at=new BlockPos(6,79,6);var result=egg.useOn(new UseOnContext(p,InteractionHand.MAIN_HAND,new BlockHitResult(new Vec3(6.5,80,6.5),Direction.UP,at,false)));check(result.consumesAction()&&egg.isEmpty(),"Survival hatch consumes once");return l.getEntitiesOfClass(Mob.class,AREA).stream().filter(m->m.getType()==type&&!before.contains(m.getUUID())).findFirst().orElseThrow();} + private static Entity spawn(ServerLevel l,EntityType type){var e=type.create(l,EntitySpawnReason.BREEDING);e.snapTo(0,80,0,0,0);if(e instanceof Mob mob)mob.finalizeSpawn(l,l.getCurrentDifficultyAt(e.blockPosition()),EntitySpawnReason.BREEDING,null);if(e instanceof AgeableMob ageable)ageable.setAge(0);l.addFreshEntity(e);return e;} + private static ItemStack oneEgg(ServerLevel l){var eggs=l.getEntitiesOfClass(ItemEntity.class,AREA);check(eggs.size()==1&&eggs.getFirst().getItem().getCount()==1,"Exactly one dropped egg "+eggs);return eggs.getFirst().getItem();} + private static void clear(ServerLevel l){for(var e:l.getEntities(null,AREA))if(!(e instanceof net.minecraft.world.entity.player.Player))e.discard();} + private static void invoke(Object o,String name){try{var m=o.getClass().getDeclaredMethod(name);m.setAccessible(true);m.invoke(o);}catch(Exception e){throw new RuntimeException(e);}} + private static KeyEvent enter(){return new KeyEvent(InputConstants.KEY_RETURN,0,0);} + private static void check(boolean ok,String message){if(!ok)throw new AssertionError(message);} +} diff --git a/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/Exploration100ClientChecks.java b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/Exploration100ClientChecks.java new file mode 100644 index 0000000..465a61e --- /dev/null +++ b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/Exploration100ClientChecks.java @@ -0,0 +1,123 @@ +package fr.koka.sanctuary.client; + +import com.mojang.blaze3d.platform.InputConstants; +import fr.koka.sanctuary.familiar.*; +import fr.koka.sanctuary.cosmetics.*; +import fr.koka.sanctuary.companion.CompanionService; +import fr.koka.sanctuary.companion.CompanionWorld; +import net.minecraft.client.KeyMapping; +import fr.koka.sanctuary.inventory.*; +import fr.koka.sanctuary.gametest.Starter039TestSupport; +import java.util.*; +import java.util.concurrent.atomic.*; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; +import net.fabricmc.fabric.api.client.gametest.v1.FabricClientGameTest; +import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.screens.worldselection.WorldCreationUiState; +import net.minecraft.client.input.KeyEvent; +import net.minecraft.core.BlockPos; +import net.minecraft.core.component.DataComponents; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.contents.TranslatableContents; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.*; +import net.minecraft.world.entity.ai.attributes.Attributes; +import net.minecraft.world.item.*; +import net.minecraft.world.item.component.CustomData; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.phys.Vec3; + +/** Server discovery snapshots, restricted catalogue, and actual idle movement plus combat regression. */ +public final class Exploration100ClientChecks implements FabricClientGameTest { + public void runTest(ClientGameTestContext c){ + var intro=new AtomicBoolean(true); + c.runOnClient(m->{m.options.renderDistance().set(8);m.options.simulationDistance().set(8);m.options.pauseOnLostFocus=false;IntroClient.setQuickTestIntro(true);}); + ClientTickEvents.END_CLIENT_TICK.register(m->{if(intro.get()&&m.gui.screen() instanceof HelloWorldScreen screen){ + Starter039TestSupport.selectFirst(screen); + screen.children().stream().filter(Button.class::isInstance).map(Button.class::cast).filter(b->b.getClass().getSimpleName().equals("ColorButton")).findFirst().orElseThrow().onPress(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("Exploration100 "+UUID.randomUUID());s.setSeed("100");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);var server=world.getServer(); + var table=new BlockPos(3,1,3); + c.runOnClient(m->imports()); + server.runOnServer(s->{var p=player(s);p.teleportTo(.5,1,.5);p.getInventory().clearContent(); + s.getCommands().performPrefixedCommand(s.createCommandSourceStack(),"sanctuary time vanilla");s.getCommands().performPrefixedCommand(s.createCommandSourceStack(),"time set night"); + for(int x=-20;x<=20;x++)for(int z=-20;z<=20;z++)p.level().setBlockAndUpdate(new BlockPos(x,0,z),Blocks.STONE.defaultBlockState()); + CompanionService.save(p,CompanionService.record(p).note("seen_mobs","")); + for(var q:fr.koka.sanctuary.multiblock.Metatable.members(table))p.level().setBlockAndUpdate(q,Blocks.CRAFTING_TABLE.defaultBlockState()); + check(fr.koka.sanctuary.multiblock.Metatable.assemble(p,table),"Assembled workshop"); + // Owning an egg is not an encounter. + p.getInventory().setItem(0,new ItemStack(Items.PIG_SPAWN_EGG)); + check(!fr.koka.sanctuary.plans.StatueDiscoveries.known(p).contains("minecraft:pig"),"Egg does not unlock statue"); + var cow=EntityTypes.COW.create(p.level(),EntitySpawnReason.COMMAND);cow.setNoAi(true);cow.setPos(.5,1,4.5);p.level().addFreshEntity(cow); + p.lookAt(net.minecraft.commands.arguments.EntityAnchorArgument.Anchor.EYES,cow.getBoundingBox().getCenter()); + fr.koka.sanctuary.companion.CompanionQueries.observeSpecies(p); + check(fr.koka.sanctuary.plans.StatueDiscoveries.known(p).contains("minecraft:cow"),"Native aimed encounter unlocks cow");cow.discard(); + p.awardStat(net.minecraft.stats.Stats.ENTITY_KILLED.get(EntityTypes.ZOMBIE)); + p.awardStat(net.minecraft.stats.Stats.ENTITY_KILLED_BY.get(EntityTypes.SKELETON)); + check(fr.koka.sanctuary.plans.StatueDiscoveries.known(p).containsAll(List.of("minecraft:cow","minecraft:zombie","minecraft:skeleton")),"All three discovery sources"); + }); + c.waitFor(m->fr.koka.sanctuary.multiblock.Metatable.state(m.level,table,true)==1,100); + server.runOnServer(s->fr.koka.sanctuary.multiblock.Metatable.open(player(s),table));c.waitForScreen(PlansScreen.class); + c.runOnClient(m->{check(MetatableClient.knows(EntityTypes.COW)&&MetatableClient.knows(EntityTypes.ZOMBIE)&&MetatableClient.knows(EntityTypes.SKELETON),"Server snapshot reaches client");check(!MetatableClient.knows(EntityTypes.PIG),"Unknown species hidden even creative"); + var before=PlanClient.plan;PlanClient.generate(EntityTypes.PIG.create(m.level,EntitySpawnReason.COMMAND),8,"mixed");check(!PlanClient.busy&&PlanClient.plan==before,"Unknown identifier cannot start generator"); + m.gui.screen().children().stream().filter(Button.class::isInstance).map(Button.class::cast).filter(b->b.getMessage().getContents() instanceof TranslatableContents t&&t.getKey().equals("sanctuary.plans.tab.statue")).findFirst().orElseThrow().onPress(enter()); + });c.takeScreenshot("exploration100-discovered-statues"); + c.runOnClient(m->{m.gui.screen().onClose();check(!MetatableClient.knows(EntityTypes.COW),"Session discovery snapshot cleared");}); + for(var item:List.of(Items.WOLF_SPAWN_EGG,Items.CHICKEN_SPAWN_EGG,Items.SLIME_SPAWN_EGG,Items.BAT_SPAWN_EGG)){ + for(String mood:List.of("calm","curious","playful")){ + var start=new AtomicReference();var distinct=new HashSet();var moved=new AtomicBoolean();var paused=new AtomicInteger(); + server.runOnServer(s->{var p=player(s);p.teleportTo(.5,1,.5);equip(p,item,mood.equals("calm")?FamiliarPersonality.PROTECTIVE:FamiliarPersonality.PACIFIST,mood,1000);var pet=FamiliarService.active(p);pet.setPos(.5,pet.motion().flies()?2.6:1,1.5);pet.stopRoaming();start.set(pet.position());}); + for(int tick=0;tick<28;tick++){ + c.waitTicks(10);server.runOnServer(s->{var p=player(s);var pet=FamiliarService.active(p);check(pet!=null,"Pet remains active"); + distinct.add(pet.blockPosition());if(pet.position().distanceToSqr(start.get())>1)moved.set(true); + if(pet.getNavigation().isDone())paused.incrementAndGet(); + check(pet.distanceToSqr(p)<100,"Idle stays near owner");check(FamiliarBattle.target(p)==null,"No unsolicited target"); + }); + } + check(moved.get()&&distinct.size()>=3,"Real wandering: "+item+" "+mood+" cells="+distinct);check(paused.get()>0,"Idle has pauses: "+item+" "+mood); + System.out.println("ROAM100_PASS "+item+" "+mood+" cells="+distinct.size()+" pauses="+paused); + } + } + } + new Autonomy074ClientChecks().runTest(c); + System.out.println("EXPLORATION100_PASS"); + } + private static void imports(){ + for(int version:List.of(2,3)){ + var root=new CompoundTag();root.putInt("Version",version);root.putInt("DataVersion",2730);root.putShort("Width",(short)1);root.putShort("Height",(short)1);root.putShort("Length",(short)1); + var blocks=version==3?new CompoundTag():root;if(version==3)root.put("Blocks",blocks); + var palette=new CompoundTag();palette.putInt("minecraft:grass",0);blocks.put("Palette",palette);blocks.putByteArray(version==3?"Data":"BlockData",new byte[]{0}); + var file=PlanClient.directory().resolve("beta100-fixture-"+version+".schem"); + try{ + java.nio.file.Files.createDirectories(file.getParent());net.minecraft.nbt.NbtIo.writeCompressed(version==3?wrap(root):root,file); + var before=java.nio.file.Files.readAllBytes(file); + var plan=fr.koka.sanctuary.plans.SchematicIO.read(file); + check(plan.cells().getFirst().state().is(Blocks.SHORT_GRASS),"Native old grass rename: Sponge "+version); + check(Arrays.equals(before,java.nio.file.Files.readAllBytes(file)),"Import preserves original bytes"); + palette.remove("minecraft:grass");palette.putInt("missing_mod:unknown",0); + try{fr.koka.sanctuary.plans.SchematicIO.decode(version==3?wrap(root):root,"Bad");throw new AssertionError("Missing mod rejected");} + catch(java.io.IOException expected){check(expected.getMessage().contains("missing_mod:unknown"),"Useful unknown block diagnostic");} + }catch(java.io.IOException e){throw new AssertionError(e);} + finally{try{java.nio.file.Files.deleteIfExists(file);}catch(java.io.IOException ignored){}} + } + System.out.println("IMPORT100_PASS Sponge v2/v3, native data fixes, missing block diagnosis, original bytes preserved"); + } + private static CompoundTag wrap(CompoundTag root){var outer=new CompoundTag();outer.put("Schematic",root);return outer;} + private static void equip(ServerPlayer p,Item item,FamiliarPersonality personality,String mood,int size){ + p.stopRiding();CarryService.detach(p);AccessoryAccess.of(p).setItem(AccessoryContainer.FAMILIAR,ItemStack.EMPTY);FamiliarService.tick(p);FamiliarBattle.release(p);p.removeAttached(FamiliarBattle.COMBAT_LOCK);p.removeAttached(FamiliarBattle.LAST_FAMILIAR); + var egg=new ItemStack(item);var scale=new CompoundTag();scale.putInt("schema",1);scale.putInt("per_mille",size);CustomData.update(DataComponents.CUSTOM_DATA,egg,t->t.put(FamiliarSize.KEY,scale)); + var profile=FamiliarBattle.world(p.level().getServer()).catalog.forEgg(egg);UUID id;do{id=UUID.randomUUID();}while(FamiliarPersonality.of(id,profile.id())!=personality||!FamiliarBond.create(id,profile,0,"test",p.getUUID()).temperament().equals(mood)); + FamiliarBattle.world(p.level().getServer()).store.put(FamiliarBond.create(id,profile,0,"test",p.getUUID()));AccessoryAccess.of(p).setItem(AccessoryContainer.FAMILIAR,FamiliarIdentity.assign(egg,id)); + CompanionService.tick(p);p.tickCount+=10-p.tickCount%10;FamiliarService.tick(p);check(FamiliarService.active(p)!=null,"Familiar spawned: "+item);p.inventoryMenu.broadcastChanges(); + } + private static Mob enemy(ServerPlayer p,double x,double y,double z){var e=EntityTypes.ZOMBIE.create(p.level(),EntitySpawnReason.COMMAND);e.setNoAi(true);e.getAttribute(Attributes.MAX_HEALTH).setBaseValue(100);e.setHealth(100);e.setPos(x,y,z);p.level().addFreshEntity(e);return e;} + private static ServerPlayer player(MinecraftServer s){return s.getPlayerList().getPlayers().getFirst();} + private static KeyEvent enter(){return new KeyEvent(InputConstants.KEY_RETURN,0,0);} + private static void check(boolean ok,String message){if(!ok)throw new AssertionError(message);} +} diff --git a/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/Fourneau085ClientChecks.java b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/Fourneau085ClientChecks.java index 95e7733..c8c1e01 100644 --- a/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/Fourneau085ClientChecks.java +++ b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/Fourneau085ClientChecks.java @@ -67,8 +67,20 @@ public final class Fourneau085ClientChecks implements FabricClientGameTest { c.waitFor(m->FourneauModels.modelAt(ORIGIN)!=null&&FourneauModels.modelAt(ORIGIN).getPath().contains(facing.getName()),200); c.waitTicks(8);c.getInput().lookAt(ORIGIN.offset(1,1,1));c.waitTicks(15); c.runOnClient(m->{for(int x=0;x<3;x++)for(int y=0;y<3;y++)for(int z=0;z<3;z++)check(FourneauModels.modelAt(ORIGIN.offset(x,y,z))!=null,"All 27 components synchronized across four chunks");}); - c.takeScreenshot("fourneau085-"+facing.getName()); + server.runOnServer(s->{var l=s.getPlayerList().getPlayers().getFirst().level(); + var f=(FurnaceBlockEntity)l.getBlockEntity(ORIGIN.offset(1,1,1)); + f.setItem(0,new ItemStack(Items.RAW_IRON,64));f.setItem(1,new ItemStack(Items.COAL,64));}); + c.waitFor(m->oven.members().stream().allMatch(p->m.level.getBlockState(p).getValue(AbstractFurnaceBlock.LIT)),200); + c.runOnClient(m->verifyFire(m,facing,true)); + c.waitTicks(15);c.takeScreenshot("fourneau094-lit-"+facing.getName()); } + server.runOnServer(s->{var l=s.getPlayerList().getPlayers().getFirst().level(); + for(var pos:oven.members()) {var f=(FurnaceBlockEntity)l.getBlockEntity(pos); + f.setItem(0,ItemStack.EMPTY);f.setItem(1,ItemStack.EMPTY);((MultiblockFurnaceAccess)f).sanctuary$heat(0);} + ((FurnaceBlockEntity)l.getBlockEntity(ORIGIN)).setItem(0,new ItemStack(Items.DIAMOND,17));}); + c.waitFor(m->oven.members().stream().noneMatch(p->m.level.getBlockState(p).getValue(AbstractFurnaceBlock.LIT)),200); + c.runOnClient(m->verifyFire(m,Direction.WEST,false)); + c.waitTicks(10);c.takeScreenshot("fourneau094-cold"); // Resource reload must retain the synchronized appearance and rebuild native models. var reload=c.computeOnClient(m->m.reloadResourcePacks());c.waitFor(m->reload.isDone()&&m.gui.overlay()==null,1200);reload.join();c.waitTicks(10); c.takeScreenshot("fourneau085-reload"); @@ -92,9 +104,41 @@ public final class Fourneau085ClientChecks implements FabricClientGameTest { c.getInput().lookAt(ORIGIN.offset(1,1,1));c.waitTicks(15);c.takeScreenshot("fourneau085-return"); server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();p.level().destroyBlock(ORIGIN.offset(2,2,2),true);}); c.waitFor(m->FourneauModels.modelAt(ORIGIN)==null,200);c.waitTicks(10);c.takeScreenshot("fourneau085-broken"); - System.out.println("FOURNEAU085_PASS: four orientations, four chunks, native models, resource reload, unload/re-entry, stock preservation, dismantle and break"); + System.out.println("FOURNEAU094_PASS: lit and cold facade, particles inside textured apertures, vanilla furnace preserved; FOURNEAU085_PASS: four orientations, four chunks, native models, resource reload, unload/re-entry, stock preservation, dismantle and break"); } } + private static void verifyFire(net.minecraft.client.Minecraft m,Direction facing,boolean lit){ + var emissions=fr.koka.sanctuary.gametest.Fourneau094Probe.emissions; + emissions.clear();fr.koka.sanctuary.gametest.Fourneau094Probe.armed=true; + try { + var random=net.minecraft.util.RandomSource.create(94); + // Real block animation entry point must replace all 27 individual furnace sources. + for(int n=0;n<50;n++)for(var pos:oven.members()){ + var state=m.level.getBlockState(pos); + check(FourneauModels.modelAt(pos,state).getPath().endsWith("_lit")==lit,"Whole facade follows shared heat"); + state.getBlock().animateTick(state,m.level,pos,random); + } + check(emissions.size()==(lit?200:0),"Exactly two flame/smoke pairs per machine, none when cold: "+emissions.size()); + var texture=javax.imageio.ImageIO.read(Fourneau085ClientChecks.class.getResourceAsStream("/assets/sanctuary/textures/block/fourneau/front_on.png")); + var original=javax.imageio.ImageIO.read(Fourneau085ClientChecks.class.getResourceAsStream("/assets/sanctuary/textures/block/fourneau/front.png")); + for(var emission:emissions){ + var q=emission.point().subtract(ORIGIN.getX(),ORIGIN.getY(),ORIGIN.getZ()); + double u=switch(facing){case NORTH->3-q.x;case SOUTH->q.x;case EAST->3-q.z;case WEST->q.z;default->throw new AssertionError();}; + double normal=switch(facing){case NORTH->-q.z;case SOUTH->q.z-3;case EAST->q.x-3;case WEST->-q.x;default->0;}; + check(Math.abs(normal-.02)<.00001,"Emission is in front of facade, never a side or interior block"); + int x=(int)(u*16),y=(int)((3-q.y)*16); + int rgb=texture.getRGB(x,y),old=original.getRGB(x,y);int r=(rgb>>16)&255,g=(rgb>>8)&255,b=rgb&255; + check(r>100&&r>b*1.5,"Emission overlaps fire texel "+x+","+y); + check((old&255)<80&&((old>>8)&255)<80&&((old>>16)&255)<80,"Emission overlaps original dark opening"); + check(y==16||y==37,"Both correct aperture heights"); + } + // A non-assembled furnace must keep the native animation. + emissions.clear();var pos=ORIGIN.offset(7,0,0); + Blocks.FURNACE.defaultBlockState().setValue(AbstractFurnaceBlock.LIT,true).getBlock().animateTick(Blocks.FURNACE.defaultBlockState().setValue(AbstractFurnaceBlock.LIT,true),m.level,pos,random); + check(emissions.size()==2,"Unassembled furnace retains native particles"); + }catch(java.io.IOException e){throw new AssertionError(e);} + finally{fr.koka.sanctuary.gametest.Fourneau094Probe.armed=false;emissions.clear();} + } private static net.minecraft.client.input.KeyEvent enter(){return new net.minecraft.client.input.KeyEvent(InputConstants.KEY_RETURN,0,0);} private static void check(boolean ok,String why){if(!ok)throw new AssertionError(why);} } diff --git a/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/Landing101ClientChecks.java b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/Landing101ClientChecks.java new file mode 100644 index 0000000..33b4a4b --- /dev/null +++ b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/Landing101ClientChecks.java @@ -0,0 +1,81 @@ +package fr.koka.sanctuary.client; + +import com.mojang.blaze3d.platform.InputConstants; +import fr.koka.sanctuary.familiar.*; +import fr.koka.sanctuary.cosmetics.*; +import fr.koka.sanctuary.companion.CompanionService; +import fr.koka.sanctuary.companion.CompanionWorld; +import net.minecraft.client.KeyMapping; +import fr.koka.sanctuary.inventory.*; +import fr.koka.sanctuary.gametest.Starter039TestSupport; +import java.util.*; +import java.util.concurrent.atomic.*; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; +import net.fabricmc.fabric.api.client.gametest.v1.FabricClientGameTest; +import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.screens.worldselection.WorldCreationUiState; +import net.minecraft.client.input.KeyEvent; +import net.minecraft.core.BlockPos; +import net.minecraft.core.component.DataComponents; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.contents.TranslatableContents; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.*; +import net.minecraft.world.entity.ai.attributes.Attributes; +import net.minecraft.world.item.*; +import net.minecraft.world.item.component.CustomData; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.phys.Vec3; + +/** Native movement contacts, fall callbacks and real attack regression for every flying profile. */ +public final class Landing101ClientChecks implements FabricClientGameTest { + public void runTest(ClientGameTestContext c){ + var intro=new AtomicBoolean(true); + c.runOnClient(m->{m.options.renderDistance().set(8);m.options.simulationDistance().set(8);m.options.pauseOnLostFocus=false;IntroClient.setQuickTestIntro(true);}); + ClientTickEvents.END_CLIENT_TICK.register(m->{if(intro.get()&&m.gui.screen() instanceof HelloWorldScreen screen){ + Starter039TestSupport.selectFirst(screen); + screen.children().stream().filter(Button.class::isInstance).map(Button.class::cast).filter(b->b.getClass().getSimpleName().equals("ColorButton")).findFirst().orElseThrow().onPress(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); + }}); + + + var descending=new AtomicReference(); + net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents.END_SERVER_TICK.register(s->{var pet=descending.get();if(pet!=null&&pet.isAlive())pet.move(MoverType.SELF,new Vec3(0,-.25,0));}); + try(var world=c.worldBuilder().adjustSettings(s->{s.setName("Landing101 "+UUID.randomUUID());s.setSeed("101");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);var server=world.getServer(); + server.runOnServer(s->{var p=player(s);p.teleportTo(.5,1,.5);p.getInventory().clearContent();s.getCommands().performPrefixedCommand(s.createCommandSourceStack(),"sanctuary time vanilla");s.getCommands().performPrefixedCommand(s.createCommandSourceStack(),"time set night");for(int x=-10;x<=10;x++)for(int z=-10;z<=10;z++)p.level().setBlockAndUpdate(new BlockPos(x,0,z),Blocks.STONE.defaultBlockState());}); + var flyers=new ArrayList();c.runOnClient(m->{for(var item:net.minecraft.core.registries.BuiltInRegistries.ITEM)if(item instanceof SpawnEggItem&&FamiliarMotion.of(item.getDefaultInstance()).flies())flyers.add(item);}); + for(var item:flyers){ + var health=new AtomicReference();var hurt=new AtomicInteger(); + server.runOnServer(s->{var p=player(s);equip(p,item,FamiliarPersonality.PACIFIST,"calm",1000);var pet=FamiliarService.active(p);pet.setNoAi(true);pet.setPos(2.5,7,2.5);pet.setDeltaMovement(Vec3.ZERO);pet.resetFallDistance();health.set(pet.getHealth());hurt.set(FamiliarBattle.state(p).lastHurt); + // Native fall callbacks can also arrive with a precomputed fall distance. + check(!pet.causeFallDamage(7,1,pet.damageSources().fall()),"Flying familiar rejects native fall callback: "+item); + check(pet.getHealth()==health.get()&&FamiliarBattle.state(p).lastHurt==hurt.get(),"No false hurt/combat from landing callback: "+item); + descending.set(pet); + }); + c.waitTicks(32); + server.runOnServer(s->{var p=player(s);var pet=descending.getAndSet(null);check(pet.onGround(),"Native contact reached ground: "+item);check(pet.getHealth()==health.get()&&pet.fallDistance==0&&FamiliarBattle.state(p).lastHurt==hurt.get(),"Slow descent remains harmless: "+item); + pet.setPos(2.5,7,2.5);pet.setOnGround(false);pet.move(MoverType.SELF,new Vec3(0,-8,0));check(pet.onGround()&&pet.getHealth()==health.get()&&FamiliarBattle.state(p).lastHurt==hurt.get(),"Fast descent also safe: "+item); + var enemy=enemy(p,4,1,2.5);check(pet.hurtServer(p.level(),p.damageSources().mobAttack(enemy),1),"Genuine hit remains accepted: "+item);check(pet.getHealth(){var p=player(s);equip(p,Items.WOLF_SPAWN_EGG,FamiliarPersonality.PACIFIST,"calm",1000);var pet=FamiliarService.active(p);float before=pet.getHealth();check(pet.causeFallDamage(7,1,pet.damageSources().fall())&&pet.getHealth()t.put(FamiliarSize.KEY,scale)); + var profile=FamiliarBattle.world(p.level().getServer()).catalog.forEgg(egg);UUID id;do{id=UUID.randomUUID();}while(FamiliarPersonality.of(id,profile.id())!=personality||!FamiliarBond.create(id,profile,0,"test",p.getUUID()).temperament().equals(mood)); + FamiliarBattle.world(p.level().getServer()).store.put(FamiliarBond.create(id,profile,0,"test",p.getUUID()));AccessoryAccess.of(p).setItem(AccessoryContainer.FAMILIAR,FamiliarIdentity.assign(egg,id)); + CompanionService.tick(p);p.tickCount+=10-p.tickCount%10;FamiliarService.tick(p);check(FamiliarService.active(p)!=null,"Familiar spawned: "+item);p.inventoryMenu.broadcastChanges(); + } + private static Mob enemy(ServerPlayer p,double x,double y,double z){var e=EntityTypes.ZOMBIE.create(p.level(),EntitySpawnReason.COMMAND);e.setNoAi(true);e.getAttribute(Attributes.MAX_HEALTH).setBaseValue(100);e.setHealth(100);e.setPos(x,y,z);p.level().addFreshEntity(e);return e;} + private static ServerPlayer player(MinecraftServer s){return s.getPlayerList().getPlayers().getFirst();} + private static KeyEvent enter(){return new KeyEvent(InputConstants.KEY_RETURN,0,0);} + private static void check(boolean ok,String message){if(!ok)throw new AssertionError(message);} +} diff --git a/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/Living105ClientChecks.java b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/Living105ClientChecks.java new file mode 100644 index 0000000..50e193b --- /dev/null +++ b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/Living105ClientChecks.java @@ -0,0 +1,86 @@ +package fr.koka.sanctuary.client; +import com.mojang.blaze3d.platform.InputConstants; +import fr.koka.sanctuary.gametest.Starter039TestSupport; +import fr.koka.sanctuary.cosmetics.*; +import fr.koka.sanctuary.multiblock.*; +import fr.koka.sanctuary.client.panorama.*; +import fr.koka.sanctuary.client.shader.VanillaLight; +import fr.koka.sanctuary.weather.*; +import java.util.*; +import java.util.concurrent.atomic.AtomicBoolean; +import java.nio.file.*; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; +import net.fabricmc.fabric.api.client.gametest.v1.*; +import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext; +import net.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.component.DataComponents; +import net.minecraft.network.chat.contents.TranslatableContents; +import net.minecraft.server.level.*; +import net.minecraft.world.*; +import net.minecraft.world.entity.*; +import net.minecraft.world.entity.animal.Animal; +import net.minecraft.world.entity.projectile.throwableitemprojectile.Snowball; +import net.minecraft.world.item.*; +import net.minecraft.world.item.component.*; +import net.minecraft.world.level.block.*; +import net.minecraft.world.level.block.entity.*; +import net.minecraft.world.phys.EntityHitResult; + +public final class Living105ClientChecks implements FabricClientGameTest { + private static int wearer,cow,pig;private static double cowStart,pigStart,flightStart; + public void runTest(ClientGameTestContext c){ + var intro=new AtomicBoolean(true); + c.runOnClient(m->{m.options.renderDistance().set(8);m.options.simulationDistance().set(8);m.options.pauseOnLostFocus=false;IntroClient.setQuickTestIntro(true);}); + ClientTickEvents.END_CLIENT_TICK.register(m->{if(intro.get()&&m.gui.screen() instanceof HelloWorldScreen s){Starter039TestSupport.selectFirst(s);s.children().stream().filter(Button.class::isInstance).map(Button.class::cast).filter(b->b.getClass().getSimpleName().equals("ColorButton")).findFirst().orElseThrow().onPress(enter());s.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("Living105 "+UUID.randomUUID());s.setSeed("105");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);var server=world.getServer(); + server.runOnServer(s->{var p=player(s);var l=p.level();s.getCommands().performPrefixedCommand(s.createCommandSourceStack(),"sanctuary time vanilla");s.getCommands().performPrefixedCommand(s.createCommandSourceStack(),"time set noon"); + for(int x=-12;x<=12;x++)for(int z=-12;z<=12;z++)l.setBlockAndUpdate(new BlockPos(x,79,z),Blocks.GRASS_BLOCK.defaultBlockState());p.teleportTo(0,80,-3);p.getInventory().clearContent(); + var m=spawn(l,EntityTypes.COW,0,80,0);m.setNoAi(true);wearer=m.getId(); + HeadEquipment.set(m,new ItemStack(Items.WRITABLE_BOOK));var original=new ArrayList>();original.add(net.minecraft.server.network.Filterable.passThrough("My original page"));HeadEquipment.get(m).set(DataComponents.WRITABLE_BOOK_CONTENT,new WritableBookContent(original)); + check(HeadCosmeticService.interact(p,m),"Book responds to native head interaction");check(pages(m)==2,"Pet adds diary entry");check(m.hurtServer(l,p.damageSources().playerAttack(p),1),"Actual hurt event");check(pages(m)==3,"Hurt adds distinct diary entry");check(HeadEquipment.get(m).get(DataComponents.WRITABLE_BOOK_CONTENT).pages().getFirst().raw().equals("My original page"),"Original text preserved"); + var shown=HeadCosmeticService.appearance(m);check(shown.get(DataComponents.WRITABLE_BOOK_CONTENT).pages().isEmpty(),"Diary not broadcast in appearance"); + p.setShiftKeyDown(true);check(MobHeadService.equip(p,m),"Retrieve written diary");check(p.getInventory().countItem(Items.WRITABLE_BOOK)==1,"Book returned once");p.setShiftKeyDown(false); + p.setItemInHand(InteractionHand.MAIN_HAND,ItemStack.EMPTY);HeadEquipment.set(m,new ItemStack(Items.WHEAT_SEEDS));var head=HeadBlockService.session(m);check(head!=null,"Crop session");head.change(((CropBlock)Blocks.WHEAT).getStateForAge(7));check(HeadBlockService.use(p,m,false),"Harvest mature head crop");check(p.getInventory().countItem(Items.WHEAT)==1,"Actual native wheat loot");check(((CropBlock)Blocks.WHEAT).getAge(head.state)==0,"Replanted after harvest"); + head.change(((CropBlock)Blocks.WHEAT).getStateForAge(7));p.setItemInHand(InteractionHand.MAIN_HAND,ItemStack.EMPTY);p.setShiftKeyDown(true);check(MobHeadService.equip(p,m),"Mature crop retrieval");p.setShiftKeyDown(false); + p.getInventory().clearContent();HeadEquipment.set(m,new ItemStack(Items.WHEAT));var a=spawn(l,EntityTypes.COW,7,80,0);cow=a.getId();cowStart=a.distanceToSqr(m); + }); + c.waitTicks(100); + server.runOnServer(s->{var m=mob(s,wearer);check(mob(s,cow).distanceToSqr(m){var p=player(s);var m=mob(s,wearer);check(mob(s,pig).distanceToSqr(m){var p=player(s);var m=mob(s,wearer);m.setNoAi(false);HeadEquipment.set(m,new ItemStack(Items.FIREWORK_ROCKET));flightStart=m.getY();snowball(p,m);check(HeadEquipment.get(m).isEmpty(),"Rocket consumed once");});c.waitTicks(18); + server.runOnServer(s->{var p=player(s);var m=mob(s,wearer);check(m.getY()>flightStart+2,"Firework lifts mob");MobHeadActions.clear(s);m.setNoAi(true);m.setPos(0,80,0);m.setDeltaMovement(0,0,0);HeadEquipment.set(m,new ItemStack(Items.SPAWNER));var head=HeadBlockService.session(m);var be=(SpawnerBlockEntity)head.entity;be.getSpawner().setEntityId(EntityTypes.PIG,p.level(),p.level().getRandom(),head.pos());var tag=be.saveWithoutMetadata(p.level().registryAccess());tag.putShort("Delay",(short)500);tag.putShort("MinSpawnDelay",(short)200);tag.putShort("MaxSpawnDelay",(short)400);be.loadWithComponents(net.minecraft.world.level.storage.TagValueInput.create(net.minecraft.util.ProblemReporter.DISCARDING,p.level().registryAccess(),tag)); + int before=p.level().getEntitiesOfClass(net.minecraft.world.entity.animal.pig.Pig.class,m.getBoundingBox().inflate(10)).size();MobHeadActions.activate(m,p.damageSources().playerAttack(p),true);int after=p.level().getEntitiesOfClass(net.minecraft.world.entity.animal.pig.Pig.class,m.getBoundingBox().inflate(10)).size();check(after>before,"Hit runs configured native spawner immediately");HeadEquipment.set(m,new ItemStack(net.minecraft.core.registries.BuiltInRegistries.ITEM.getValue(net.minecraft.resources.Identifier.withDefaultNamespace("lightning_rod")))); + s.getCommands().performPrefixedCommand(s.createCommandSourceStack(),"sanctuary weather thunder");p.level().setThunderLevel(1);p.level().setRainLevel(1); + var at=HeadBlockService.session(m).pos();check(MobHeadActions.lightningTarget(p.level(),at.offset(5,0,0),at.offset(5,0,0)).equals(at.above()),"Mobile rod attracts lightning");var bolt=EntityTypes.LIGHTNING_BOLT.create(p.level(),EntitySpawnReason.COMMAND);bolt.setVisualOnly(true);bolt.setPos(at.getX()+.5,at.getY()+1,at.getZ()+.5);p.level().addFreshEntity(bolt); + });c.waitTicks(2); + server.runOnServer(s->{check(HeadBlockService.session(mob(s,wearer)).emitsPower(),"Native lightning powers head rod");});c.waitTicks(60); + server.runOnServer(s->{var p=player(s);var m=mob(s,wearer);check(!HeadBlockService.session(m).emitsPower(),"Lightning pulse resets");HeadEquipment.set(m,ItemStack.EMPTY);m.discard();s.getCommands().performPrefixedCommand(s.createCommandSourceStack(),"sanctuary weather snow");p.level().setRainLevel(1);var at=new BlockPos(9,80,9);check(p.level().getBiome(at).value().shouldSnow(p.level(),at),"Temperate biome permits seasonal accumulation"); + try{var tick=ServerLevel.class.getDeclaredMethod("tickPrecipitation",BlockPos.class);tick.setAccessible(true);for(int i=0;i<200;i++)tick.invoke(p.level(),at);}catch(Exception e){throw new RuntimeException(e);}check(p.level().getBlockState(at).is(Blocks.SNOW),"Native precipitation accumulates snow"); + });c.waitFor(m->WeatherClient.state().kind()==DailyWeather.Kind.SNOW,200); + c.runOnClient(m->check(m.level.getPrecipitationAt(new BlockPos(9,81,9))==net.minecraft.world.level.biome.Biome.Precipitation.SNOW,"Client snow render and audio query")); + server.runOnServer(s->{var p=player(s);s.getCommands().performPrefixedCommand(s.createCommandSourceStack(),"sanctuary weather clear");var origin=new BlockPos(3,80,1);for(int x=0;x<3;x++)for(int y=0;y<3;y++)for(int z=0;z<3;z++)p.level().setBlockAndUpdate(origin.offset(x,y,z),Blocks.BARREL.defaultBlockState());var f=new MultiblockData.Machine(UUID.randomUUID(),origin,false,Direction.NORTH);check(Multiblocks.assemble(p,f),"Assemble fût");Multiblocks.open(p,f,origin,0,"",0);}); + c.waitFor(m->m.gui.screen() instanceof MultiblockScreen,200);c.runOnClient(m->{var screen=(MultiblockScreen)m.gui.screen();var a=screen.extraAreas().getFirst();check(screen.mouseScrolled(a.getX()+5,a.getY()+5,0,-1),"Wheel handled");});c.waitFor(m->m.player.containerMenu instanceof MultiblockMenu menu&&menu.view.page()==1,200); + c.runOnClient(m->{var screen=(MultiblockScreen)m.gui.screen();var a=screen.extraAreas().getFirst();screen.mouseScrolled(a.getX()+5,a.getY()+5,0,100);});c.waitFor(m->m.player.containerMenu instanceof MultiblockMenu menu&&menu.view.page()==0,200); + server.runOnServer(s->player(s).closeContainer());c.waitFor(m->m.gui.screen()==null,200); + c.waitTicks(30);var captures=new ArrayList(); + for(int saturation:new int[]{0,200}){c.runOnClient(m->{VanillaLight.setEnabled(true);VanillaLight.setSaturation(saturation);PanoramaOptions.get().resolution=256;});c.waitTicks(4);c.runOnClient(PanoramaCapture::request);c.waitFor(m->PanoramaCapture.lastCapture().isDone(),400);captures.add(c.computeOnClient(m->PanoramaCapture.lastCapture().join()));} + verifyPanorama(captures);c.runOnClient(m->{VanillaLight.setSaturation(142);PanoramaOptions.get().resolution=1024;}); + System.out.println("LIVING105_NATIVE_PASS: diary, crops, lures, snowball, flint, firework, spawner, lightning, snow, barrel scroll, shader panoramas "+captures); + }finally{intro.set(false);} + } + private static void verifyPanorama(List captures){try{double[] chroma=new double[2];for(int n=0;n<2;n++){var meta=com.google.gson.JsonParser.parseString(Files.readString(captures.get(n).resolve("capture.json"))).getAsJsonObject();check(meta.getAsJsonArray("post_effects").toString().contains("vanilla_light"),"Active effect captured");for(int f=0;f<6;f++){double before=chroma[n];var image=javax.imageio.ImageIO.read(captures.get(n).resolve("panorama_"+f+".png").toFile());for(int y=0;y<256;y++)for(int x=0;x<256;x++){int rgb=image.getRGB(x,y),r=(rgb>>16)&255,g=(rgb>>8)&255,b=rgb&255;chroma[n]+=Math.max(r,Math.max(g,b))-Math.min(r,Math.min(g,b));}if(n==0)check(chroma[n]-before<100000,"Every face is desaturated: "+f+" "+(chroma[n]-before));}var raw=java.nio.ByteBuffer.wrap(Files.readAllBytes(captures.get(n).resolve("depth_5.f32"))).order(java.nio.ByteOrder.LITTLE_ENDIAN);int surfaces=0;while(raw.hasRemaining())if(raw.getFloat()>0)surfaces++;check(surfaces>30000,"Postprocessing preserves terrain depth");}check(chroma[1]>chroma[0]+100000,"Captured saturation changes actual pixels "+Arrays.toString(chroma));}catch(Exception e){throw new RuntimeException(e);}} + private static void snowball(ServerPlayer p,Mob m){var ball=new Snowball(p.level(),p,new ItemStack(Items.SNOWBALL));try{var hit=Snowball.class.getDeclaredMethod("onHitEntity",EntityHitResult.class);hit.setAccessible(true);hit.invoke(ball,new EntityHitResult(m));}catch(Exception e){throw new RuntimeException(e);}} + private static int pages(Mob m){return HeadEquipment.get(m).get(DataComponents.WRITABLE_BOOK_CONTENT).pages().size();} + private static T spawn(ServerLevel l,EntityType type,double x,double y,double z){var m=type.create(l,EntitySpawnReason.COMMAND);m.setPos(x,y,z);l.addFreshEntity(m);return m;} + private static ServerPlayer player(net.minecraft.server.MinecraftServer s){return s.getPlayerList().getPlayers().getFirst();} + private static Mob mob(net.minecraft.server.MinecraftServer s,int id){return (Mob)s.overworld().getEntity(id);} + private static KeyEvent enter(){return new KeyEvent(InputConstants.KEY_RETURN,0,0);} + private static void check(boolean ok,String message){if(!ok)throw new AssertionError(message);} +} diff --git a/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/Melt109ClientChecks.java b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/Melt109ClientChecks.java new file mode 100644 index 0000000..069ef5c --- /dev/null +++ b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/Melt109ClientChecks.java @@ -0,0 +1,105 @@ +package fr.koka.sanctuary.client; + +import com.mojang.blaze3d.platform.InputConstants; +import fr.koka.sanctuary.gametest.Starter039TestSupport; +import fr.koka.sanctuary.progression.SanctuaryGameplay; +import fr.koka.sanctuary.realtime.RealtimeService; +import fr.koka.sanctuary.weather.SeasonalSnowMelt; +import fr.koka.sanctuary.weather.SeasonalWeather; +import fr.koka.sanctuary.weather.WeatherService; +import java.time.Clock; +import java.time.Instant; +import java.time.ZoneOffset; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicBoolean; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; +import net.fabricmc.fabric.api.client.gametest.v1.FabricClientGameTest; +import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext; +import net.fabricmc.fabric.api.client.gametest.v1.world.TestWorldSave; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.screens.worldselection.WorldCreationUiState; +import net.minecraft.client.input.KeyEvent; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.contents.TranslatableContents; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.SnowLayerBlock; +import net.minecraft.world.level.gamerules.GameRules; + +public final class Melt109ClientChecks implements FabricClientGameTest { + private static final BlockPos AT=new BlockPos(4,80,4), PERSIST=new BlockPos(5,80,12); + public void runTest(ClientGameTestContext c) { + new Snow108ClientChecks().runTest(c); + var intro=new AtomicBoolean(true); + ClientTickEvents.END_CLIENT_TICK.register(m->{if(intro.get()&&m.gui.screen() instanceof HelloWorldScreen screen){ + Starter039TestSupport.selectFirst(screen); + screen.children().stream().filter(Button.class::isInstance).map(Button.class::cast).filter(b->b.getClass().getSimpleName().equals("ColorButton")).findFirst().orElseThrow().onPress(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()); + }}); + TestWorldSave saved; + try { + try(var world=c.worldBuilder().adjustSettings(s->{ + s.setName("Melt109 "+UUID.randomUUID());s.setSeed("109");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()); + s.getGameRules().set(SanctuaryGameplay.ENABLED,true,null); + }).create()) { + saved=world.getWorldSave(); + c.waitFor(m->m.player!=null&&m.gui.screen()==null&&ProgressionClient.active()&&!IntroSpawnFlash.active(),1200); + world.getServer().runOnServer(server->{ + var p=server.getPlayerList().getPlayers().getFirst();var l=p.level();p.teleportTo(0,80,-5); + for(int x=-15;x<=15;x++)for(int z=-15;z<=15;z++)l.setBlockAndUpdate(new BlockPos(x,79,z),Blocks.STONE.defaultBlockState()); + clock(l,"2026-07-10T12:00:00Z",SeasonalWeather.Season.SUMMER); + check(l.getGameRules().get(SeasonalSnowMelt.ENABLED),"Melt enabled by default"); + deposit(l,AT,16);check(l.getBlockState(AT.above()).is(Blocks.SNOW_BLOCK),"Default two-block accumulation retained"); + clear(l);step(l,AT,1); + check(layers(l,AT.above())==7&&layers(l,AT)==8,"Summer melts only one top layer"); + step(l,AT,7);check(l.getBlockState(AT.above()).isAir()&&layers(l,AT)==8,"Top block melts completely before the lower block"); + step(l,AT,8);check(l.getBlockState(AT).isAir()&&!tracked(l,AT),"Natural column vanishes without water; receipt removed"); + + var built=new BlockPos(7,80,4);l.setBlockAndUpdate(built,Blocks.SNOW_BLOCK.defaultBlockState());step(l,built,64);check(layers(l,built)==8,"Untracked snow block preserved"); + var old=new BlockPos(8,80,4);snow(l,old,5);step(l,old,64);check(layers(l,old)==5,"Untracked old layers preserved"); + var base=new BlockPos(9,80,4);snow(l,base,3);deposit(l,base,5);clear(l);step(l,base,16);check(layers(l,base)==3&&!tracked(l,base),"Weather above manual layers melts only back to its base"); + var atop=new BlockPos(9,80,6);l.setBlockAndUpdate(atop,Blocks.SNOW_BLOCK.defaultBlockState());deposit(l,atop,8);clear(l);step(l,atop,16);check(layers(l,atop)==8&&l.getBlockState(atop.above()).isAir(),"Weather on a snow construction melts; construction stays"); + var replaced=new BlockPos(10,80,4);deposit(l,replaced,8);l.setBlockAndUpdate(replaced,Blocks.AIR.defaultBlockState());l.setBlockAndUpdate(replaced,Blocks.SNOW_BLOCK.defaultBlockState());clear(l);step(l,replaced,32);check(layers(l,replaced)==8&&!tracked(l,replaced),"Break and replacement cannot inherit melt permission"); + var thickened=new BlockPos(10,80,6);deposit(l,thickened,2);snow(l,thickened,6);clear(l);step(l,thickened,32);check(layers(l,thickened)==6&&!tracked(l,thickened),"Manual layer edit relinquishes natural provenance"); + var capped=new BlockPos(12,80,6);deposit(l,capped,8);snow(l,capped.above(),1);clear(l);step(l,capped,32);check(layers(l,capped)==8&&layers(l,capped.above())==1,"Manual thin snow cap protects its natural support"); + var roof=new BlockPos(11,80,4);deposit(l,roof,8);l.setBlockAndUpdate(roof.above(3),Blocks.STONE.defaultBlockState());clear(l);step(l,roof,32);check(layers(l,roof)==8,"Covered snow remains untouched");l.setBlockAndUpdate(roof.above(3),Blocks.AIR.defaultBlockState());step(l,roof,1);check(layers(l,roof)==7,"Uncovered natural snow resumes melt"); + command(l,"gamerule sanctuary:seasonal_snow_melt false");step(l,roof,32);check(layers(l,roof)==7,"Operator disables melting");command(l,"gamerule sanctuary:seasonal_snow_melt true"); + clock(l,"2026-01-10T12:00:00Z",SeasonalWeather.Season.WINTER);clear(l);step(l,roof,64);check(layers(l,roof)==7,"Winter never melts even with clear weather"); + clock(l,"2026-04-10T12:00:00Z",SeasonalWeather.Season.SPRING);clear(l);step(l,roof,64);check(l.getBlockState(roof).isAir(),"Spring progressively melts"); + deposit(l,roof,8);clock(l,"2026-10-10T12:00:00Z",SeasonalWeather.Season.AUTUMN);clear(l);step(l,roof,128);check(l.getBlockState(roof).isAir(),"Autumn progressively melts"); + deposit(l,roof,8);clock(l,"2026-07-10T12:00:00Z",SeasonalWeather.Season.SUMMER); + command(l,"sanctuary weather snow");l.getWeatherData().setRaining(false);l.setRainLevel(0);step(l,roof,64);check(layers(l,roof)==8,"Forced summer snowfall blocks melting"); + command(l,"sanctuary weather snow_showers");l.getWeatherData().setRaining(false);l.setRainLevel(0);step(l,roof,64);check(layers(l,roof)==8,"Pauses in snow showers also block melting"); + clear(l);command(l,"sanctuary weather vanilla");step(l,roof,64);check(layers(l,roof)==8,"Vanilla mode does not apply seasonal melt"); + var cold=new BlockPos(-4,80,-4);deposit(l,cold,8);command(l,"fillbiome -8 76 -8 -1 88 -1 minecraft:snowy_plains");clear(l);step(l,cold,64);check(layers(l,cold)==8,"Permanently cold biome retains natural snow"); + deposit(l,PERSIST,16);clear(l);command(l,"gamerule sanctuary:seasonal_snow_melt false"); + check(tracked(l,PERSIST)&&tracked(l,PERSIST.above()),"Both persisted blocks have weather provenance"); + }); + } + try(var world=saved.open()) { + c.waitFor(m->m.player!=null&&m.gui.screen()==null&&ProgressionClient.active(),1200); + world.getConnection().waitForChunksDownload();c.waitTicks(20); + world.getServer().runOnServer(server->{ + var l=server.overworld();clock(l,"2026-07-10T12:00:00Z",SeasonalWeather.Season.SUMMER);clear(l); + check(!l.getGameRules().get(SeasonalSnowMelt.ENABLED),"Melt rule persists across restart"); + check(layers(l,PERSIST)==8&&layers(l,PERSIST.above())==8&&tracked(l,PERSIST)&&tracked(l,PERSIST.above()),"Chunk attachment survives native save/reload"); + command(l,"gamerule sanctuary:seasonal_snow_melt true");step(l,PERSIST,16); + check(l.getBlockState(PERSIST).isAir()&&l.getBlockState(PERSIST.above()).isAir()&&!tracked(l,PERSIST),"Loaded natural snow melts and clears provenance: base="+l.getBlockState(PERSIST)+", top="+l.getBlockState(PERSIST.above())+", sky="+l.canSeeSky(PERSIST.above(2))+", active="+WeatherService.active(l)+", biome="+l.getBiome(PERSIST)+", season="+WeatherService.season(server)); + check(layers(l,new BlockPos(7,80,4))==8&&layers(l,new BlockPos(9,80,4))==3,"Built and preexisting snow preserved across restart"); + }); + } + System.out.println("MELT109_NATIVE_PASS: accumulation regression, top-down melt, seasons, profiles, cold biomes, constructions, edit invalidation, gamerule, native chunk save/reload"); + }finally{intro.set(false);} + } + private static void deposit(ServerLevel l,BlockPos pos,int count){l.getGameRules().set(GameRules.MAX_SNOW_ACCUMULATION_HEIGHT,1,l.getServer());command(l,"sanctuary weather snow");l.getWeatherData().setRaining(true);l.setRainLevel(1);step(l,pos,count);l.getGameRules().set(GameRules.MAX_SNOW_ACCUMULATION_HEIGHT,0,l.getServer());} + private static void clear(ServerLevel l){command(l,"sanctuary weather clear");l.getWeatherData().setRaining(false);l.setRainLevel(0);} + private static void clock(ServerLevel l,String time,SeasonalWeather.Season expected){try{var field=RealtimeService.Session.class.getDeclaredField("clock");field.setAccessible(true);field.set(RealtimeService.session(l.getServer()),Clock.fixed(Instant.parse(time),ZoneOffset.UTC));check(WeatherService.season(l.getServer())==expected,"Test calendar: "+expected);}catch(ReflectiveOperationException e){throw new AssertionError(e);}} + private static boolean tracked(ServerLevel l,BlockPos p){var m=l.getChunkAt(p).getAttached(SeasonalSnowMelt.NATURAL);return m!=null&&m.containsKey(Long.toString(p.asLong()));} + private static void snow(ServerLevel l,BlockPos p,int layers){l.setBlockAndUpdate(p,Blocks.SNOW.defaultBlockState().setValue(SnowLayerBlock.LAYERS,layers));} + private static int layers(ServerLevel l,BlockPos p){return SeasonalSnowMelt.layers(l.getBlockState(p));} + private static void step(ServerLevel l,BlockPos p,int n){for(int i=0;im.gui.screen() instanceof ArenaScreen&&ArenaClient.view!=null,100); c.getInput().pressKey(InputConstants.KEY_ESCAPE);c.waitTicks(3);c.runOnClient(m->check(m.gui.screen() instanceof PauseScreen,"Arena returns to pause")); c.runOnClient(m->{ArenaClient.open(m.gui.screen());m.gui.screen().onClose();});c.waitTicks(8);c.runOnClient(m->check(m.gui.screen() instanceof PauseScreen,"Late arena response does not reopen it")); - click(c,"sanctuary.progression.progress");c.runOnClient(m->{check(find(m.gui.screen(),"sanctuary.arena.title")==null,"No combat in progression");check(find(m.gui.screen(),"sanctuary.cycle.faction")==null,"No faction in progression");for(String tab:List.of("cycle","log"))button(m.gui.screen(),"sanctuary.cycle."+tab);button(m.gui.screen(),"gui.advancements");}); + click(c,"sanctuary.progression.progress");c.runOnClient(m->{check(find(m.gui.screen(),"sanctuary.arena.title")==null,"No combat in progression");check(find(m.gui.screen(),"sanctuary.cycle.faction")==null,"No faction in progression");for(String tab:List.of("cycle","log"))button(m.gui.screen(),"sanctuary.cycle."+tab);check(widgets(m.gui.screen()).anyMatch(InlineAdvancements.class::isInstance),"Embedded native tree");}); c.takeScreenshot("menus083-progress-"+locale+"-"+scale); - c.runOnClient(m->{var screen=(ProgressionScreen)m.gui.screen();check(button(screen,"sanctuary.cycle.cycle").getY()>=screen.contentTop+12,"XP has separate line");check(button(screen,"gui.advancements").getX()check(m.gui.screen() instanceof net.minecraft.client.gui.screens.advancements.AdvancementsScreen,"Full native advancements"));c.getInput().pressKey(InputConstants.KEY_ESCAPE);c.waitTicks(3);c.runOnClient(m->check(m.gui.screen() instanceof ProgressionScreen,"Advancements returns to progression"));c.getInput().pressKey(InputConstants.KEY_ESCAPE);c.waitTicks(3); + c.runOnClient(m->{var screen=(ProgressionScreen)m.gui.screen();check(button(screen,"sanctuary.cycle.cycle").getY()>=screen.contentTop+12,"XP has separate line");check(widgets(screen).filter(InlineAdvancements.class::isInstance).map(InlineAdvancements.class::cast).findFirst().orElseThrow().getX(){m.options.guiScale().set(2);m.resizeGui();m.getLanguageManager().setSelected("fr_fr");m.getLanguageManager().onResourceManagerReload(m.getResourceManager());}); click(c,"sanctuary.progression.blocks");click(c,"sanctuary.discovery.recipes");click(c,"sanctuary.recipes.buy");c.waitFor(m->RecipeClient.knowledge!=null&&RecipeClient.knowledge.learned(),100); diff --git a/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/Metatable096ClientChecks.java b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/Metatable096ClientChecks.java new file mode 100644 index 0000000..0d4f42d --- /dev/null +++ b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/Metatable096ClientChecks.java @@ -0,0 +1,190 @@ +package fr.koka.sanctuary.client; + +import com.mojang.blaze3d.platform.InputConstants; +import fr.koka.sanctuary.gametest.Starter039TestSupport; +import fr.koka.sanctuary.multiblock.*; +import fr.koka.sanctuary.plans.*; +import java.util.*; +import java.util.concurrent.atomic.*; +import java.util.stream.Stream; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; +import net.fabricmc.fabric.api.client.gametest.v1.*; +import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext; +import net.minecraft.client.gui.components.*; +import net.minecraft.client.gui.components.events.*; +import net.minecraft.client.gui.screens.*; +import net.minecraft.client.gui.screens.worldselection.WorldCreationUiState; +import net.minecraft.client.input.KeyEvent; +import net.minecraft.core.*; +import net.minecraft.network.chat.contents.TranslatableContents; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.item.ItemEntity; +import net.minecraft.world.item.*; +import net.minecraft.world.level.*; +import net.minecraft.world.level.block.*; +import net.minecraft.world.phys.*; + +/** Physical workshop, selection boundary, native paid construction and reversible nine-cell assembly. */ +public final class Metatable096ClientChecks implements FabricClientGameTest { + private static final BlockPos O=new BlockPos(15,80,15); + public void runTest(ClientGameTestContext c){ + var intro=new AtomicBoolean(true); + c.runOnClient(m->{m.options.renderDistance().set(6);m.options.simulationDistance().set(5);m.options.pauseOnLostFocus=false;IntroClient.setQuickTestIntro(true);}); + ClientTickEvents.END_CLIENT_TICK.register(m->{if(intro.get()&&m.gui.screen() instanceof HelloWorldScreen screen){ + Starter039TestSupport.selectFirst(screen); + widgets(screen).filter(Button.class::isInstance).map(Button.class::cast).filter(b->b.getClass().getSimpleName().equals("ColorButton")).findFirst().orElseThrow().onPress(enter()); + button(screen,"sanctuary.progression.enter").onPress(enter());intro.set(false); + }}); + try(var world=c.worldBuilder().adjustSettings(s->{s.setName("Metatable096 "+UUID.randomUUID());s.setSeed("96");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); + var server=world.getServer(); + server.runOnServer(s->{var p=player(s);fr.koka.sanctuary.companion.CompanionService.save(p,fr.koka.sanctuary.companion.CompanionService.record(p).note("seen_mobs","creeper"));var l=p.level();p.setGameMode(GameType.CREATIVE);p.setNoGravity(true);p.getAbilities().flying=true;p.onUpdateAbilities(); + s.getCommands().performPrefixedCommand(s.createCommandSourceStack(),"sanctuary time vanilla");s.getCommands().performPrefixedCommand(s.createCommandSourceStack(),"time set noon"); + for(int x=9;x<27;x++)for(int z=9;z<27;z++)l.setBlockAndUpdate(new BlockPos(x,80,z),Blocks.STONE.defaultBlockState()); + for(var pos:Metatable.members(O))l.setBlockAndUpdate(pos,Blocks.CRAFTING_TABLE.defaultBlockState()); + p.getInventory().setItem(p.getInventory().getSelectedSlot(),ItemStack.EMPTY);home(p); + }); + c.waitFor(m->m.level.getBlockState(O).is(Blocks.CRAFTING_TABLE)&&m.player.getY()>80,200); + c.runOnClient(m->{m.options.guiScale().set(2);m.resizeGui();m.options.bobView().set(false);m.getWindow().setWindowed(1280,800);}); + c.getInput().pressKey(InputConstants.KEY_K);c.waitTicks(3);c.runOnClient(m->check(m.gui.screen()==null,"K cannot open catalogue without visiting workshop")); + server.runOnServer(s->{var p=player(s);Metatable.wrench(p,O);check(Metatable.state(p.level(),O,false)==1,"First click only previews");Metatable.wrench(p,O.offset(1,0,1));check(Metatable.state(p.level(),O,true)==1,"Second click assembles nine parts");}); + c.waitFor(m->Metatable.state(m.level,O,true)==1,200); + open(c);c.runOnClient(m->{check(((PlansScreen)m.gui.screen()).isWorkshop(),"Physical interaction opens workshop");for(String t:List.of("statue","machines","buildings","library"))button(m.gui.screen(),"sanctuary.plans.tab."+t);}); + c.takeScreenshot("metabli096-catalog-en"); + c.runOnClient(m->{for(String category:List.of("recent","infrastructure","decoration"))button(m.gui.screen(),"sanctuary.plans.tab."+category);}); + c.runOnClient(m->button(m.gui.screen(),"sanctuary.plans.tab.decoration").onPress(enter()));c.takeScreenshot("catalog099-decoration-en"); + c.runOnClient(m->button(m.gui.screen(),"sanctuary.plans.tab.statue").onPress(enter()));c.takeScreenshot("catalog099-statues-en"); + + c.runOnClient(m->{button(m.gui.screen(),"sanctuary.plans.tab.machines").onPress(enter());});c.takeScreenshot("metabli096-machines-en"); + c.runOnClient(m->button(m.gui.screen(),"sanctuary.plans.catalog.piston").onPress(enter())); + c.waitFor(m->m.gui.screen()==null&&PlanClient.plan!=null,100); + c.runOnClient(m->check(PlanClient.plan.cells().size()==9&&!MetatableClient.available(),"Selection closes catalogue and releases selection access")); + cockpit(c); + var selected=new AtomicReference(); + c.runOnClient(m->{var screen=(PlansScreen)m.gui.screen();check(!screen.isWorkshop(),"K opens only field controls"); + check(widgets(screen).filter(Button.class::isInstance).map(Button.class::cast).noneMatch(b->key(b).startsWith("sanctuary.plans.tab.")||Set.of("sanctuary.plans.clear","sanctuary.plans.generate").contains(key(b))),"No remote catalogue or abandon controls"); + selected.set(PlanClient.plan);var origin=PlanClient.origin;button(screen,"sanctuary.plans.move.east").onPress(enter());check(PlanClient.origin.equals(origin.east()),"Move by one block"); + button(screen,"sanctuary.plans.rotate").onPress(enter());check(PlanClient.plan.width()==1&&PlanClient.plan.depth()==3,"Rotation transforms footprint"); + for(int i=0;i<3;i++)PlanClient.rotate();check(PlanClient.plan.equals(selected.get()),"Four rotations restore plan"); + PlanClient.catalog(ConstructionCatalog.ENTRIES.getFirst());check(PlanClient.plan.equals(selected.get()),"Remote selection refused"); + screen.onFilesDrop(List.of(PlanClient.directory().resolve("missing.schem")));check(!PlanClient.busy,"Remote import refused before reading files"); + }); + c.takeScreenshot("metabli096-controls-en");close(c); + // Tap K anchors at the actual 96-block ray hit, repeat rotates around that same anchor. + c.getInput().lookAt(new BlockPos(23,80,23));c.waitTicks(3); + var tapped=new AtomicReference(); + c.getInput().pressKey(InputConstants.KEY_K);c.waitTicks(3); + c.runOnClient(m->{check(m.gui.screen()==null,"K tap never opens cockpit");var hit=(BlockHitResult)m.player.pick(96,1,false);var target=hit.getBlockPos().relative(hit.getDirection());check(PlanClient.origin.equals(target.offset(-PlanClient.plan.width()/2,0,-PlanClient.plan.depth()/2)),"K positions at the looked-at face, not near the player");tapped.set(PlanClient.origin);selected.set(PlanClient.plan);}); + c.getInput().pressKey(InputConstants.KEY_K);c.waitTicks(3); + c.runOnClient(m->check(PlanClient.plan.equals(selected.get().rotate()),"Second K on same point rotates exactly once")); + c.getInput().lookAt(new BlockPos(16,250,13));c.waitTicks(3);c.runOnClient(m->tapped.set(PlanClient.origin)); + c.getInput().pressKey(InputConstants.KEY_K);c.waitTicks(3);c.runOnClient(m->check(PlanClient.origin.equals(tapped.get()),"Looking into the sky keeps the old placement")); + cockpit(c);c.runOnClient(m->check(PlanClient.origin.equals(tapped.get()),"Holding K opens controls without moving or rotating"));close(c); + + // Walking away from the workstation never grants catalogue access through K. + server.runOnServer(s->player(s).teleportTo(24,82,24));c.waitFor(m->m.player.getX()>23,100); + cockpit(c);c.runOnClient(m->check(!((PlansScreen)m.gui.screen()).isWorkshop(),"Field options work away from the table"));close(c); + server.runOnServer(s->home(player(s)));c.waitFor(m->m.player.getZ()<15,100);open(c); + c.runOnClient(m->button(m.gui.screen(),"sanctuary.plans.tab.infrastructure").onPress(enter()));c.takeScreenshot("metabli096-buildings-en"); + c.runOnClient(m->button(m.gui.screen(),"sanctuary.plans.catalog.footbridge").onPress(enter()));c.waitFor(m->m.gui.screen()==null&&PlanClient.plan.depth()==7,100); + c.runOnClient(m->{PlanClient.origin=new BlockPos(20,81,15);PlanClient.refresh();check(PlanClient.completed==0,"Preview changes no world blocks");}); + server.runOnServer(s->{var p=player(s);p.setGameMode(GameType.SURVIVAL);p.setNoGravity(false);p.getInventory().setItem(p.getInventory().getSelectedSlot(),new ItemStack(Items.OAK_PLANKS,2));p.teleportTo(20.5,81,13.5);}); + c.waitFor(m->!m.player.isCreative()&&m.player.getX()>20&&m.player.getMainHandItem().is(Items.OAK_PLANKS),150); + c.getInput().lookAt(new BlockPos(20,80,15));c.waitTicks(3);c.getInput().holdKey(o->o.keyUse);c.waitTicks(1);c.getInput().releaseKey(o->o.keyUse); + c.waitFor(m->PlanClient.completed==1,150); + server.runOnServer(s->check(player(s).getMainHandItem().getCount()==1,"Native manual placement consumes one object")); + c.takeScreenshot("metabli096-manual-preview"); + // A catalogue visit is required again. Closing while a statue computes cancels that selection. + server.runOnServer(s->{var p=player(s);p.getInventory().setItem(p.getInventory().getSelectedSlot(),ItemStack.EMPTY);home(p);}); + c.waitFor(m->m.player.getX()<18&&m.player.getMainHandItem().isEmpty(),100);open(c); + c.runOnClient(m->{selected.set(PlanClient.plan);configure(m.gui.screen());button(m.gui.screen(),"sanctuary.plans.generate").onPress(enter());m.gui.screen().onClose();}); + c.waitFor(m->!PlanClient.busy,400);c.runOnClient(m->check(PlanClient.plan==selected.get(),"Closing workshop discards asynchronous new selection")); + open(c);c.runOnClient(m->{configure(m.gui.screen());button(m.gui.screen(),"sanctuary.plans.generate").onPress(enter());}); + c.waitFor(m->!PlanClient.busy&&PlanClient.plan!=selected.get()&&m.gui.screen()==null,400); + c.runOnClient(m->check(PlanClient.plan.height()==32&&PlanClient.plan.cells().size()>100,"Statue generation is available at the workshop")); + open(c);language(c,"fr_fr");c.takeScreenshot("metabli096-catalog-fr"); + close(c); + server.runOnServer(s->{var p=player(s);var holder=s.getAdvancements().get(net.minecraft.resources.Identifier.withDefaultNamespace("story/mine_stone"));for(String criterion:holder.value().criteria().keySet())p.getAdvancements().award(holder,criterion);}); + c.waitTicks(10);c.runOnClient(m->m.gui.setScreen(new ProgressionScreen(null)));c.waitTicks(6);c.takeScreenshot("catalog099-progression-fr"); + c.runOnClient(m->{var screen=m.gui.screen();check(widgets(screen).anyMatch(InlineAdvancements.class::isInstance),"Native advancements embedded under skills");check(widgets(screen).filter(Button.class::isInstance).map(Button.class::cast).noneMatch(b->key(b).equals("gui.advancements")),"No separate advancements navigation button");}); + c.runOnClient(m->{try{ + var embedded=widgets(m.gui.screen()).filter(InlineAdvancements.class::isInstance).map(InlineAdvancements.class::cast).findFirst().orElseThrow(); + var method=InlineAdvancements.class.getDeclaredMethod("tab");method.setAccessible(true);var tab=(net.minecraft.client.gui.screens.advancements.AdvancementTab)method.invoke(embedded); + var access=(fr.koka.sanctuary.mixin.client.NotificationAdvancementTabAccess)tab; + var node=access.sanctuary$widgets().values().stream().filter(w->NotificationClient.canTrack(w.getAdvancement())).findFirst().orElseThrow(); + tab.scroll(100-node.getX()-access.sanctuary$scrollX(),45-node.getY()-access.sanctuary$scrollY()); + float scale=Math.min(1,(embedded.getWidth()-4)/234f);int x=embedded.getX()+(embedded.getWidth()-Math.round(234*scale))/2,y=embedded.getY()+27; + var event=new net.minecraft.client.input.MouseButtonEvent(x+(Math.floor(access.sanctuary$scrollX())+node.getX()+12)*scale,y+(Math.floor(access.sanctuary$scrollY())+node.getY()+12)*scale,new net.minecraft.client.input.MouseButtonInfo(1,0)); + boolean before=NotificationClient.isPinned(node.getAdvancement().id());m.gui.screen().mouseClicked(event,false);m.gui.screen().mouseReleased(event); + check(NotificationClient.isPinned(node.getAdvancement().id())!=before,"Embedded tree right-click toggles notification tracking"); + }catch(ReflectiveOperationException e){throw new AssertionError(e);}}); + c.runOnClient(m->{m.options.guiScale().set(3);m.resizeGui();});c.waitTicks(5);c.takeScreenshot("catalog099-progression-scale3-fr"); + c.runOnClient(m->{m.options.guiScale().set(2);m.resizeGui();});close(c);open(c); + + // Export/import remains available, but file drops only select at the workshop. + c.runOnClient(m->PlanClient.export());c.waitFor(m->!PlanClient.busy,200); + c.runOnClient(m->{button(m.gui.screen(),"sanctuary.plans.tab.library").onPress(enter());});c.takeScreenshot("metabli096-library-fr"); + c.runOnClient(m->{try(var files=java.nio.file.Files.list(PlanClient.directory())){ + var path=files.filter(p->p.toString().endsWith(".litematic")).findFirst().orElseThrow();((PlansScreen)m.gui.screen()).onFilesDrop(List.of(path)); + }catch(java.io.IOException e){throw new AssertionError(e);}}); + c.waitFor(m->!PlanClient.busy&&m.gui.screen()==null,300); + cockpit(c);c.takeScreenshot("metabli096-controls-fr");close(c); + // Breaking the workshop while its menu is open invalidates selection, but retains the player's plan. + open(c);c.runOnClient(m->selected.set(PlanClient.plan)); + server.runOnServer(s->{var p=player(s);p.level().destroyBlock(O,true); + check(Metatable.members(O).stream().filter(q->!q.equals(O)).allMatch(q->p.level().getBlockState(q).is(Blocks.CRAFTING_TABLE)),"Eight remaining parts restore without drops"); + int drops=p.level().getEntitiesOfClass(ItemEntity.class,new AABB(O).inflate(4)).stream().filter(e->e.getItem().is(Items.CRAFTING_TABLE)).mapToInt(e->e.getItem().getCount()).sum();check(drops==1,"Broken component drops one crafting table"); + }); + c.waitFor(m->m.gui.screen()==null,100);c.runOnClient(m->check(PlanClient.plan==selected.get(),"Table destruction leaves existing preview intact")); + server.runOnServer(s->{var p=player(s);p.level().setBlockAndUpdate(O,Blocks.CRAFTING_TABLE.defaultBlockState());check(Metatable.assemble(p,O),"Reassemble");}); + c.waitFor(m->Metatable.state(m.level,O,true)==1,100); + server.runOnServer(s->{var p=player(s);p.setGameMode(GameType.CREATIVE);p.setNoGravity(true);p.getAbilities().flying=true;p.onUpdateAbilities();p.teleportTo(21.5,84,12.5);}); + c.waitTicks(8);c.getInput().lookAt(O.offset(1,0,1));c.waitTicks(10);c.runOnClient(m->{PlanClient.visible=false;if(!m.gui.hud.isHidden())m.gui.hud.toggle();});c.waitTicks(8);c.takeScreenshot("metabli096-textures"); + server.runOnServer(s->player(s).teleportTo(400,90,400));c.waitFor(m->m.level.getChunkSource().getChunkNow(O.getX()>>4,O.getZ()>>4)==null,600); + server.runOnServer(s->home(player(s)));c.waitFor(m->Metatable.state(m.level,O,true)==1,500); + server.waitFor(s->Metatable.state(player(s).level(),O,true)==1,200); + server.runOnServer(s->{Metatable.dissolve(player(s).level(),O);check(Metatable.state(player(s).level(),O,false)==1,"Dissociation restores all nine original blocks");}); + server.runOnServer(s->{ + var l=player(s).level(); + for(boolean desert:new boolean[]{true,false}){ + var holder=l.registryAccess().lookupOrThrow(net.minecraft.core.registries.Registries.STRUCTURE).getOrThrow(net.minecraft.resources.ResourceKey.create(net.minecraft.core.registries.Registries.STRUCTURE,net.minecraft.resources.Identifier.withDefaultNamespace(desert?"desert_pyramid":"jungle_pyramid"))); + net.minecraft.world.level.levelgen.structure.StructurePiece piece=desert?new net.minecraft.world.level.levelgen.structure.structures.DesertPyramidPiece(net.minecraft.util.RandomSource.create(97),0,0):new net.minecraft.world.level.levelgen.structure.structures.JungleTemplePiece(net.minecraft.util.RandomSource.create(97),0,0); + var start=new net.minecraft.world.level.levelgen.structure.StructureStart(holder.value(),new ChunkPos(0,0),0,new net.minecraft.world.level.levelgen.structure.pieces.PiecesContainer(List.of(piece))); + ((fr.koka.sanctuary.expansion.NativeStructureMetadata23)(Object)start).sanctuary$native23(new fr.koka.sanctuary.expansion.NativeStructureMetadata23.Data(97,-64)); + Temple097ClientChecks.foundationChecks(l,start); + } + var p=player(s);p.teleportTo(18.5,84,8);p.setDeltaMovement(Vec3.ZERO); + }); + c.waitTicks(10); + c.runOnClient(m->{ + var states=List.of(Blocks.COBBLESTONE.defaultBlockState(),Blocks.OAK_PLANKS.defaultBlockState(),Blocks.BRICKS.defaultBlockState(),Blocks.OAK_LOG.defaultBlockState(),Blocks.FURNACE.defaultBlockState(),Blocks.CHEST.defaultBlockState(),Blocks.OAK_STAIRS.defaultBlockState(),Blocks.OAK_LEAVES.defaultBlockState()); + var cells=new ArrayList();for(int i=0;i{PlanClient.rotate();check(PlanClient.plan.width()==1&&PlanClient.plan.depth()==8,"Native textured block states rotate with the plan");PlanClient.visible=false;}); + c.waitTicks(5);c.takeScreenshot("preview097-hidden"); + System.out.println("PREVIEW097_PASS: native textured materials, stairs and container models, rotation, visibility; optional temple foundation clipping and NBT for desert and jungle"); + language(c,"en_us"); + System.out.println("METATABLE096_PASS: two-click assembly, four-chunk sync, physical catalogue, one selection, field controls, no remote import/switch, native paid placement, asynchronous cancellation, statue + import, FR/EN, destruction/drop/reassembly/unload/dissociation"); + } + } + private static void home(ServerPlayer p){p.teleportTo(16.5,81,13.8);p.setDeltaMovement(Vec3.ZERO);} + private static ServerPlayer player(MinecraftServer s){return s.getPlayerList().getPlayers().getFirst();} + private static void open(ClientGameTestContext c){ + c.runOnClient(m->{var at=O.offset(1,0,0);m.gameMode.useItemOn(m.player,net.minecraft.world.InteractionHand.MAIN_HAND,new BlockHitResult(Vec3.atCenterOf(at).add(0,.5,0),Direction.UP,at,false));}); + c.waitFor(m->m.gui.screen() instanceof PlansScreen screen&&screen.isWorkshop(),150); + } + private static void close(ClientGameTestContext c){c.runOnClient(m->{if(m.gui.screen()!=null)m.gui.screen().onClose();});} + private static void cockpit(ClientGameTestContext c){c.getInput().holdKey(InputConstants.KEY_K);c.waitTicks(12);c.getInput().releaseKey(InputConstants.KEY_K);c.waitForScreen(PlansScreen.class);} + private static void configure(Screen screen){button(screen,"sanctuary.plans.tab.statue").onPress(enter());button(screen,"sanctuary.plans.configure").onPress(enter());widgets(screen).filter(EditBox.class::isInstance).map(EditBox.class::cast).findFirst().orElseThrow().setValue("minecraft:creeper");} + private static void language(ClientGameTestContext c,String lang){var reload=c.computeOnClient(m->{m.getLanguageManager().setSelected(lang);return m.reloadResourcePacks();});c.waitFor(m->reload.isDone()&&m.gui.overlay()==null,1200);reload.join();} + private static Stream widgets(GuiEventListener node){return Stream.concat(Stream.of(node),node instanceof ContainerEventHandler c?c.children().stream().flatMap(Metatable096ClientChecks::widgets):Stream.empty());} + private static String key(Button b){return b.getMessage().getContents() instanceof TranslatableContents t?t.getKey():b.getMessage().getString();} + private static Button button(Screen s,String key){return widgets(s).filter(Button.class::isInstance).map(Button.class::cast).filter(b->key(b).equals(key)).findFirst().orElseThrow(()->new AssertionError("Missing button "+key));} + private static KeyEvent enter(){return new KeyEvent(InputConstants.KEY_RETURN,0,0);} + private static void check(boolean ok,String why){if(!ok)throw new AssertionError(why);} +} diff --git a/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/Plans072ClientChecks.java b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/Plans072ClientChecks.java index 986ed0f..81e641a 100644 --- a/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/Plans072ClientChecks.java +++ b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/Plans072ClientChecks.java @@ -3,6 +3,7 @@ package fr.koka.sanctuary.client; import com.mojang.blaze3d.platform.InputConstants; import fr.koka.sanctuary.gametest.Starter039TestSupport; import fr.koka.sanctuary.plans.*; +import fr.koka.sanctuary.multiblock.Metatable; import java.util.*; import java.util.concurrent.atomic.*; import java.util.stream.Stream; @@ -56,13 +57,16 @@ public final class Plans072ClientChecks implements FabricClientGameTest { }); require(supported.containsAll(List.of("minecraft:creeper","minecraft:cow","minecraft:pig","minecraft:chicken","minecraft:zombie","minecraft:sheep")),"Common native models are supported"); System.out.println("PLANS072 models="+supported.size()+" refused="+refused+" capture_ms="+(System.nanoTime()-started)/1_000_000); - c.getInput().pressKey(InputConstants.KEY_K);c.waitForScreen(PlansScreen.class); + server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var o=new BlockPos(0,9,2);for(var q:Metatable.members(o))p.level().setBlockAndUpdate(q,Blocks.CRAFTING_TABLE.defaultBlockState());fr.koka.sanctuary.companion.CompanionService.save(p,fr.koka.sanctuary.companion.CompanionService.record(p).note("seen_mobs","creeper"));require(Metatable.assemble(p,o),"Assemble the physical workshop");}); + c.waitFor(m->Metatable.state(m.level,new BlockPos(0,9,2),true)==1,150); + server.runOnServer(s->Metatable.open(s.getPlayerList().getPlayers().getFirst(),new BlockPos(0,9,2)));c.waitForScreen(PlansScreen.class); c.takeScreenshot("plans072-create-en"); - c.runOnClient(m->button(m.gui.screen(),"sanctuary.plans.generate").onPress(enter())); + c.runOnClient(m->{configure(m.gui.screen());button(m.gui.screen(),"sanctuary.plans.generate").onPress(enter());}); c.waitFor(m->PlanClient.plan!=null&&!PlanClient.busy,400); c.runOnClient(m->{require(PlanClient.plan.height()==32,"Requested model height");require(PlanClient.plan.cells().size()>100,"Detailed geometry becomes real material cells"); PlanClient.origin=new BlockPos(10,2,10);PlanClient.refresh(); }); + cockpit(c); c.takeScreenshot("plans072-plan-en"); var expected=new AtomicReference();c.runOnClient(m->expected.set(PlanClient.plan)); c.runOnClient(m->button(m.gui.screen(),"sanctuary.plans.rotate").onPress(enter())); @@ -74,19 +78,49 @@ public final class Plans072ClientChecks implements FabricClientGameTest { c.runOnClient(m->m.gui.setScreen(null));c.waitFor(m->m.player.distanceToSqr(15.5,15,55.5)<2,100); c.getInput().lookAt(new BlockPos(15,18,17));c.waitTicks(15); c.getInput().pressKey(InputConstants.KEY_F1);c.takeScreenshot("plans072-ghost-creeper");c.getInput().pressKey(InputConstants.KEY_F1); - c.getInput().pressKey(InputConstants.KEY_K);c.waitForScreen(PlansScreen.class); - c.runOnClient(m->button(m.gui.screen(),"sanctuary.plans.paste").onPress(enter()));c.waitForScreen(ConfirmScreen.class); - c.clickScreenButton("gui.yes"); + cockpit(c); + // Creative builds use the same native controls and confirmation as a player. + c.runOnClient(m->button(m.gui.screen(),"sanctuary.plans.paste").onPress(enter())); + c.waitForScreen(ConfirmScreen.class);c.takeScreenshot("plans098-confirm-en"); + c.runOnClient(m->button(m.gui.screen(),"gui.no").onPress(enter())); + c.waitForScreen(PlansScreen.class); + server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();require(p.level().getBlockState(new BlockPos(10,2,10).offset(expected.get().cells().getFirst().pos())).isAir(),"Cancel leaves plan unbuilt");}); + c.runOnClient(m->button(m.gui.screen(),"sanctuary.plans.paste").onPress(enter())); + c.waitForScreen(ConfirmScreen.class); + c.runOnClient(m->button(m.gui.screen(),"gui.yes").onPress(enter())); c.waitFor(m->PlanClient.completed==PlanClient.plan.cells().size(),400); server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();for(var cell:expected.get().cells())require(p.level().getBlockState(new BlockPos(10,2,10).offset(cell.pos()))==cell.state(),"Server pasted every exact material");}); c.runOnClient(m->m.gui.setScreen(null));c.getInput().pressKey(InputConstants.KEY_F1);c.waitTicks(10);c.takeScreenshot("plans072-built-creeper");c.getInput().pressKey(InputConstants.KEY_F1); + // Select real machine/building catalogue entries, then build through K. + for(String id:List.of("fourneau","shelter")){ + c.waitTicks(25); // Respect the server's one-second upload rate limit. + var origin=new BlockPos(id.equals("fourneau")?30:40,4,10); + server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();p.teleportTo(.5,10,.5);}); + c.waitFor(m->m.player.distanceToSqr(.5,10,.5)<1,100); + server.runOnServer(s->Metatable.open(s.getPlayerList().getPlayers().getFirst(),new BlockPos(0,9,2)));c.waitForScreen(PlansScreen.class); + c.runOnClient(m->{button(m.gui.screen(),"sanctuary.plans.tab."+(id.equals("fourneau")?"machines":"buildings")).onPress(enter());button(m.gui.screen(),"sanctuary.plans.catalog."+id).onPress(enter());}); + c.waitFor(m->m.gui.screen()==null,100); + c.runOnClient(m->{PlanClient.origin=origin;PlanClient.rotate();PlanClient.layer=0;PlanClient.refresh();expected.set(PlanClient.plan);}); + cockpit(c); + c.runOnClient(m->button(m.gui.screen(),"sanctuary.plans.paste").onPress(enter()));c.waitForScreen(ConfirmScreen.class); + c.runOnClient(m->button(m.gui.screen(),"gui.yes").onPress(enter())); + c.waitFor(m->PlanClient.completed==PlanClient.plan.cells().size(),200); + server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();for(var cell:expected.get().cells())require(p.level().getBlockState(origin.offset(cell.pos()))==cell.state(),"Full rotated catalogue plan built: "+id);}); + } + // Losing creative while the confirmation is open must neither build nor keep its button. + c.runOnClient(m->{PlanClient.select(new BuildPlan("Mode change",1,1,1,List.of(new BuildPlan.Cell(BlockPos.ZERO,Blocks.STONE.defaultBlockState()))),Map.of());PlanClient.origin=new BlockPos(4,2,4);PlanClient.refresh();}); + cockpit(c); + c.runOnClient(m->button(m.gui.screen(),"sanctuary.plans.paste").onPress(enter()));c.waitForScreen(ConfirmScreen.class); // A real survival connection must reject an upload, even when sent without the GUI. server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();p.setGameMode(GameType.SURVIVAL);p.getAbilities().invulnerable=true;}); c.waitFor(m->!m.player.isCreative(),100); + c.runOnClient(m->button(m.gui.screen(),"gui.yes").onPress(enter()));c.waitForScreen(PlansScreen.class); + c.runOnClient(m->{require(widgets(m.gui.screen()).filter(Button.class::isInstance).map(Button.class::cast).noneMatch(b->key(b).equals("sanctuary.plans.paste")),"Creative button disappears after mode change");m.gui.screen().onClose();}); + server.runOnServer(s->require(s.getPlayerList().getPlayers().getFirst().level().getBlockState(new BlockPos(4,2,4)).isAir(),"No build after losing creative")); c.runOnClient(m->{var tiny=new BuildPlan("Survival",1,1,1,List.of(new BuildPlan.Cell(BlockPos.ZERO,Blocks.STONE.defaultBlockState())));PlanClient.select(tiny,Map.of());PlanClient.origin=new BlockPos(4,2,4);PlanClient.refresh(); ClientPlayNetworking.send(new PlanPayload(UUID.randomUUID(),PlanClient.origin,1,1,1,1,0,tiny.cells()));}); c.waitTicks(30);server.runOnServer(s->require(s.getPlayerList().getPlayers().getFirst().level().getBlockState(new BlockPos(4,2,4)).isAir(),"Survival spoofed paste leaves the world untouched")); - c.getInput().pressKey(InputConstants.KEY_K);c.waitForScreen(PlansScreen.class); + cockpit(c); c.runOnClient(m->require(widgets(m.gui.screen()).filter(Button.class::isInstance).map(Button.class::cast).noneMatch(b->key(b).equals("sanctuary.plans.paste")),"Survival UI only offers construction guidance")); language(c,"fr_fr");c.takeScreenshot("plans072-survie-fr"); c.runOnClient(m->{PlanClient.layer=0;PlanClient.refresh();require(PlanClient.completed==0,"Free preview does not complete a block");}); @@ -97,15 +131,21 @@ public final class Plans072ClientChecks implements FabricClientGameTest { c.runOnClient(m->m.gui.setScreen(null)); c.waitFor(m->m.player.distanceToSqr(4.5,1,3.5)<1&&m.player.getMainHandItem().is(Items.STONE),150); c.getInput().lookAt(new BlockPos(4,1,4));c.waitTicks(3); - c.runOnClient(m->System.out.println("PLANS072 survival hit="+m.hitResult+" player="+m.player.position()+" origin="+PlanClient.origin+" held="+m.player.getMainHandItem())); - c.getInput().holdKey(o->o.keyUse);c.waitTicks(1);c.getInput().releaseKey(o->o.keyUse); + c.runOnClient(m->System.out.println("PLANS072 survival hit="+m.hitResult+" target="+(m.hitResult instanceof net.minecraft.world.phys.BlockHitResult h?h.getBlockPos()+" "+h.getDirection():"none")+" player="+m.player.position()+" origin="+PlanClient.origin+" held="+m.player.getMainHandItem())); + c.runOnClient(m->m.gameMode.useItemOn(m.player,net.minecraft.world.InteractionHand.MAIN_HAND, + new net.minecraft.world.phys.BlockHitResult(new Vec3(4.5,2,4.5),Direction.UP,new BlockPos(4,1,4),false))); c.waitFor(m->PlanClient.completed==1,150); server.runOnServer(s->require(s.getPlayerList().getPlayers().getFirst().getMainHandItem().getCount()==1,"Survival native placement consumes exactly one block")); - c.runOnClient(m->{PlanClient.clear();m.gui.setScreen(new PlansScreen(null));});c.takeScreenshot("plans072-create-fr"); + c.runOnClient(m->PlanClient.clear()); + server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();p.setGameMode(GameType.CREATIVE);p.getAbilities().flying=true;p.onUpdateAbilities();p.teleportTo(.5,10,.5);}); + c.waitFor(m->m.player.distanceToSqr(.5,10,.5)<1,100); + server.runOnServer(s->Metatable.open(s.getPlayerList().getPlayers().getFirst(),new BlockPos(0,9,2)));c.waitForScreen(PlansScreen.class);c.takeScreenshot("plans072-create-fr"); language(c,"en_us"); - System.out.println("PLANS072 passed: native models + UV textures + active palette, creation widgets, 32-block statue, rotations, materials, gzip export, colored preview, real creative network paste, survival spoof rejection and paid native placement, FR/EN."); + System.out.println("PLANS072 passed: native models + UV textures + active palette, creation widgets, 32-block statue, rotations, materials, gzip export, colored preview, creative confirmation/cancel, statue/machine/building construction, mode change rejection, survival spoof rejection and paid native placement, FR/EN."); } } + private static void cockpit(ClientGameTestContext c){c.getInput().holdKey(InputConstants.KEY_K);c.waitTicks(12);c.getInput().releaseKey(InputConstants.KEY_K);c.waitForScreen(PlansScreen.class);} + private static void configure(Screen screen){button(screen,"sanctuary.plans.tab.statue").onPress(enter());button(screen,"sanctuary.plans.configure").onPress(enter());widgets(screen).filter(EditBox.class::isInstance).map(EditBox.class::cast).findFirst().orElseThrow().setValue("minecraft:creeper");} private static void language(ClientGameTestContext c,String id){ var reload=c.computeOnClient(m->{m.getLanguageManager().setSelected(id);return m.reloadResourcePacks();}); c.waitFor(m->reload.isDone(),600);reload.join();c.waitFor(m->m.gui.overlay()==null,400);c.waitTicks(8); diff --git a/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/Schematics100ClientChecks.java b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/Schematics100ClientChecks.java new file mode 100644 index 0000000..4946d9e --- /dev/null +++ b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/Schematics100ClientChecks.java @@ -0,0 +1,82 @@ +package fr.koka.sanctuary.client; + +import com.mojang.blaze3d.platform.InputConstants; +import fr.koka.sanctuary.familiar.*; +import fr.koka.sanctuary.cosmetics.*; +import fr.koka.sanctuary.companion.CompanionService; +import fr.koka.sanctuary.companion.CompanionWorld; +import net.minecraft.client.KeyMapping; +import fr.koka.sanctuary.inventory.*; +import fr.koka.sanctuary.gametest.Starter039TestSupport; +import java.util.*; +import java.util.concurrent.atomic.*; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; +import net.fabricmc.fabric.api.client.gametest.v1.FabricClientGameTest; +import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.screens.worldselection.WorldCreationUiState; +import net.minecraft.client.input.KeyEvent; +import net.minecraft.core.BlockPos; +import net.minecraft.core.component.DataComponents; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.contents.TranslatableContents; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.*; +import net.minecraft.world.entity.ai.attributes.Attributes; +import net.minecraft.world.item.*; +import net.minecraft.world.item.component.CustomData; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.phys.Vec3; + +/** Imported files never become world data; rejected imports survive safe world transitions. */ +public final class Schematics100ClientChecks implements FabricClientGameTest { + public void runTest(ClientGameTestContext c){ + var intro=new AtomicBoolean(true); + c.runOnClient(m->{m.options.renderDistance().set(8);m.options.simulationDistance().set(8);m.options.pauseOnLostFocus=false;IntroClient.setQuickTestIntro(true);}); + ClientTickEvents.END_CLIENT_TICK.register(m->{if(intro.get()&&m.gui.screen() instanceof HelloWorldScreen screen){ + Starter039TestSupport.selectFirst(screen); + screen.children().stream().filter(Button.class::isInstance).map(Button.class::cast).filter(b->b.getClass().getSimpleName().equals("ColorButton")).findFirst().orElseThrow().onPress(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); + }}); + + + var bad=PlanClient.directory().resolve("beta100-bad.schem");var good=PlanClient.directory().resolve("beta100-good.schem"); + var table=new BlockPos(3,1,3); + net.fabricmc.fabric.api.client.gametest.v1.world.TestWorldSave saved; + try{ + java.nio.file.Files.createDirectories(bad.getParent());write(bad,"missing_mod:block");write(good,"minecraft:stone"); + byte[] original=java.nio.file.Files.readAllBytes(bad); + try(var world=c.worldBuilder().adjustSettings(s->{s.setName("Schematics100 "+UUID.randomUUID());s.setSeed("100");s.setAllowCommands(true);s.setGameMode(WorldCreationUiState.SelectedGameMode.CREATIVE);s.setWorldType(s.getNormalPresetList().stream().filter(e->e.preset()!=null&&e.preset().getRegisteredName().equals("sanctuary_test:flat")).findFirst().orElseThrow());}).create()){ + saved=world.getWorldSave();c.waitFor(m->ProgressionClient.active()&&m.player!=null&&m.gui.screen()==null&&!IntroSpawnFlash.active(),1200); + world.getServer().runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();p.teleportTo(.5,1,.5);for(var q:fr.koka.sanctuary.multiblock.Metatable.members(table))p.level().setBlockAndUpdate(q,Blocks.CRAFTING_TABLE.defaultBlockState());check(fr.koka.sanctuary.multiblock.Metatable.assemble(p,table),"Workshop");}); + c.waitFor(m->fr.koka.sanctuary.multiblock.Metatable.state(m.level,table,true)==1,100); + world.getServer().runOnServer(s->fr.koka.sanctuary.multiblock.Metatable.open(s.getPlayerList().getPlayers().getFirst(),table));c.waitForScreen(PlansScreen.class); + c.runOnClient(m->PlanClient.load(bad));c.waitFor(m->!PlanClient.busy,100); + c.runOnClient(m->{check(m.gui.screen() instanceof PlansScreen&&PlanClient.plan==null,"Invalid import stays in workshop");check(PlanClient.status.getString().contains("missing_mod:block"),"Error names incompatible block");}); + c.takeScreenshot("schematics100-rejected-import"); + c.runOnClient(m->PlanClient.load(good));c.waitFor(m->!PlanClient.busy&&PlanClient.plan!=null&&m.gui.screen()==null,100); + check(Arrays.equals(original,java.nio.file.Files.readAllBytes(bad)),"Rejected file unchanged"); + } + try(var world=saved.open()){ + c.waitFor(m->m.player!=null&&m.gui.screen()==null,1200); + c.runOnClient(m->check(PlanClient.plan==null&&!PlanClient.busy,"No stale plan after reload")); + world.getServer().runOnServer(s->check(fr.koka.sanctuary.multiblock.Metatable.state(s.overworld(),table,true)==1,"Same world and table survived reload")); + } + intro.set(true); + try(var second=c.worldBuilder().adjustSettings(s->{s.setName("Schematics100 second "+UUID.randomUUID());s.setSeed("101");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); + c.runOnClient(m->check(PlanClient.plan==null&&!MetatableClient.available(),"Independent second world opens without stale plan/session")); + } + }catch(java.io.IOException e){throw new AssertionError(e);} + finally{try{java.nio.file.Files.deleteIfExists(bad);java.nio.file.Files.deleteIfExists(good);}catch(java.io.IOException ignored){}} + System.out.println("SCHEMATICS100_PASS rejected asynchronous import, useful diagnostic, valid import, save/reopen, source bytes preserved"); + } + private static void write(java.nio.file.Path path,String id)throws java.io.IOException{ + var root=new CompoundTag();root.putInt("Version",3);root.putInt("DataVersion",net.minecraft.SharedConstants.getCurrentVersion().dataVersion().version());root.putShort("Width",(short)1);root.putShort("Height",(short)1);root.putShort("Length",(short)1); + var palette=new CompoundTag();palette.putInt(id,0);var blocks=new CompoundTag();blocks.put("Palette",palette);blocks.putByteArray("Data",new byte[]{0});root.put("Blocks",blocks);var outer=new CompoundTag();outer.put("Schematic",root);net.minecraft.nbt.NbtIo.writeCompressed(outer,path); + } + private static KeyEvent enter(){return new KeyEvent(InputConstants.KEY_RETURN,0,0);} + private static void check(boolean ok,String message){if(!ok)throw new AssertionError(message);} +} diff --git a/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/Snow108ClientChecks.java b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/Snow108ClientChecks.java new file mode 100644 index 0000000..a5493b0 --- /dev/null +++ b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/Snow108ClientChecks.java @@ -0,0 +1,87 @@ +package fr.koka.sanctuary.client; + +import com.mojang.blaze3d.platform.InputConstants; +import fr.koka.sanctuary.gametest.Starter039TestSupport; +import fr.koka.sanctuary.progression.SanctuaryGameplay; +import fr.koka.sanctuary.weather.SnowAccumulation; +import java.util.UUID; +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.BlockPos; +import net.minecraft.network.chat.contents.TranslatableContents; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.EntitySpawnReason; +import net.minecraft.world.entity.EntityTypes; +import net.minecraft.world.level.LightLayer; +import net.minecraft.world.level.block.*; +import net.minecraft.world.level.gamerules.GameRules; +import net.minecraft.world.phys.shapes.CollisionContext; + +public final class Snow108ClientChecks implements FabricClientGameTest { + private static final BlockPos AT = new BlockPos(4,80,4); + public void runTest(ClientGameTestContext c) { + var intro=new AtomicBoolean(true); + c.runOnClient(m->{m.options.renderDistance().set(6);m.options.simulationDistance().set(5);m.options.pauseOnLostFocus=false;IntroClient.setQuickTestIntro(true);}); + ClientTickEvents.END_CLIENT_TICK.register(m->{if(intro.get()&&m.gui.screen() instanceof HelloWorldScreen screen){ + Starter039TestSupport.selectFirst(screen); + screen.children().stream().filter(Button.class::isInstance).map(Button.class::cast).filter(b->b.getClass().getSimpleName().equals("ColorButton")).findFirst().orElseThrow().onPress(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()); + }}); + try { + for(boolean enabled:new boolean[]{true,false})try(var world=c.worldBuilder().adjustSettings(s->{ + s.setName("Snow108 "+enabled+" "+UUID.randomUUID());s.setSeed("108");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()); + s.getGameRules().set(SanctuaryGameplay.ENABLED,enabled,null); + }).create()) { + c.waitFor(m->m.player!=null&&m.gui.screen()==null&&(!enabled||ProgressionClient.active()&&!IntroSpawnFlash.active()),1200); + world.getServer().runOnServer(server->{ + var p=server.getPlayerList().getPlayers().getFirst();var l=p.level();p.teleportTo(0,80,-5); + for(int x=-15;x<=15;x++)for(int z=-15;z<=15;z++)l.setBlockAndUpdate(new BlockPos(x,79,z),Blocks.STONE.defaultBlockState()); + if(enabled) { + command(l,"sanctuary weather snow");l.setRainLevel(1); + check(l.getGameRules().get(SnowAccumulation.MAX_HEIGHT)==2,"Default two blocks"); + for(int i=1;i<=7;i++){l.tickPrecipitation(AT);check(layers(l,AT)==i,"One layer per precipitation event: "+i);} + var pig=EntityTypes.PIG.create(l,EntitySpawnReason.COMMAND);pig.setNoAi(true);pig.setNoGravity(true);pig.setPos(4.5,80.75,4.5);l.addFreshEntity(pig); + l.tickPrecipitation(AT);check(l.getBlockState(AT).is(Blocks.SNOW_BLOCK),"Eighth layer becomes solid snow block"); + check(l.getBlockState(AT).getCollisionShape(l,AT,CollisionContext.empty()).bounds().maxY==1,"Full block collision"); + check(pig.getY()>=81,"Growth lifts a standing entity");pig.discard(); + l.tickPrecipitation(AT);check(layers(l,AT.above())==1,"New layer begins above solid snow"); + for(int i=0;i<30;i++)l.tickPrecipitation(AT); + check(l.getBlockState(AT.above()).is(Blocks.SNOW_BLOCK)&&l.getBlockState(AT.above(2)).isAir(),"Default cap at two blocks"); + command(l,"gamerule sanctuary:snow_accumulation_height 4"); + for(int i=0;i<30;i++)l.tickPrecipitation(AT); + check(l.getBlockState(AT.above(3)).is(Blocks.SNOW_BLOCK)&&l.getBlockState(AT.above(4)).isAir(),"Operator raises cap to four blocks"); + command(l,"gamerule sanctuary:snow_accumulation_height -1"); + for(int i=0;i<16;i++)l.tickPrecipitation(AT); + check(l.getBlockState(AT.above(5)).is(Blocks.SNOW_BLOCK),"Unlimited mode continues upward"); + var fresh=new BlockPos(-5,80,-5);command(l,"gamerule sanctuary:snow_accumulation_height 0");l.tickPrecipitation(fresh);check(l.getBlockState(fresh).isAir(),"Sanctuary zero stops deposits"); + command(l,"gamerule sanctuary:snow_accumulation_height 2");l.getGameRules().set(GameRules.MAX_SNOW_ACCUMULATION_HEIGHT,0,server);l.tickPrecipitation(fresh);check(l.getBlockState(fresh).isAir(),"Native zero remains authoritative");l.getGameRules().set(GameRules.MAX_SNOW_ACCUMULATION_HEIGHT,1,server); + l.setRainLevel(0);l.getWeatherData().setRaining(false);l.tickPrecipitation(fresh);check(l.getBlockState(fresh).isAir(),"No accumulation between showers"); + command(l,"sanctuary weather snow_showers");l.setRainLevel(1);l.getWeatherData().setRaining(true);l.tickPrecipitation(fresh);check(layers(l,fresh)==1,"Snow showers accumulate while falling"); + var flower=new BlockPos(-6,80,-6);l.setBlockAndUpdate(flower.below(),Blocks.GRASS_BLOCK.defaultBlockState());l.setBlockAndUpdate(flower,Blocks.DANDELION.defaultBlockState());l.tickPrecipitation(flower);check(l.getBlockState(flower).is(Blocks.DANDELION),"Plant preserved"); + var water=new BlockPos(-7,79,-7);l.setBlockAndUpdate(water,Blocks.WATER.defaultBlockState());l.tickPrecipitation(water);check(l.getBlockState(water).is(Blocks.WATER)&&l.getBlockState(water.above()).isAir(),"Open water not filled with snow"); + var roof=new BlockPos(7,83,-7);l.setBlockAndUpdate(roof,Blocks.STONE.defaultBlockState());l.tickPrecipitation(roof);check(layers(l,roof.above())==1&&l.getBlockState(roof.below()).isAir(),"Snow on roof, not indoors"); + var cauldron=new BlockPos(8,80,-8);l.setBlockAndUpdate(cauldron,Blocks.CAULDRON.defaultBlockState());for(int i=0;i<200;i++)l.tickPrecipitation(cauldron);check(l.getBlockState(cauldron).is(Blocks.POWDER_SNOW_CAULDRON),"Native cauldron snow filling preserved"); + command(l,"sanctuary weather rain");l.setRainLevel(1);var rainy=new BlockPos(10,80,0);l.tickPrecipitation(rainy);check(l.getBlockState(rainy).isAir(),"Temperate rain is not snow"); + command(l,"sanctuary weather vanilla");cold(l);for(int i=0;i<8;i++)l.tickPrecipitation(new BlockPos(0,80,0));check(l.getBlockState(new BlockPos(0,80,0)).is(Blocks.SNOW_BLOCK),"Cold biome also accumulates with vanilla weather"); + var light=new BlockPos(11,80,11);l.setBlockAndUpdate(light.below(),Blocks.GLOWSTONE.defaultBlockState());command(l,"sanctuary weather snow");l.setRainLevel(1); + } else { + cold(l);for(int i=0;i<40;i++)l.tickPrecipitation(AT);check(layers(l,AT)==1&&l.getBlockState(AT.above()).isAir(),"Non-Sanctuary world keeps native one-layer limit"); + } + }); + if(enabled){c.waitTicks(8);world.getServer().runOnServer(s->{var l=s.overworld();var light=new BlockPos(11,80,11);check(l.getBrightness(LightLayer.BLOCK,light)>=10,"Test light propagated");for(int i=0;i<16;i++)l.tickPrecipitation(light);check(l.getBlockState(light).isAir(),"Bright surfaces do not accumulate");});} + } + System.out.println("SNOW108_NATIVE_PASS: layers, solid blocks, vertical stacking, entity lift, caps and commands, weather, light, supports, cauldron and vanilla world"); + }finally{intro.set(false);} + } + private static void cold(ServerLevel l){command(l,"fillbiome -8 76 -8 8 88 8 minecraft:snowy_plains");l.getWeatherData().setRaining(true);l.setRainLevel(1);} + private static int layers(ServerLevel l,BlockPos p){var s=l.getBlockState(p);return s.is(Blocks.SNOW)?s.getValue(SnowLayerBlock.LAYERS):0;} + private static void command(ServerLevel l,String s){l.getServer().getCommands().performPrefixedCommand(l.getServer().createCommandSourceStack(),s);} + private static KeyEvent enter(){return new KeyEvent(InputConstants.KEY_RETURN,0,0);} + private static void check(boolean ok,String message){if(!ok)throw new AssertionError(message);} +} diff --git a/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/Statuary106ClientChecks.java b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/Statuary106ClientChecks.java new file mode 100644 index 0000000..94d709e --- /dev/null +++ b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/Statuary106ClientChecks.java @@ -0,0 +1,158 @@ +package fr.koka.sanctuary.client; + +import com.google.gson.*; +import com.mojang.blaze3d.platform.InputConstants; +import fr.koka.sanctuary.gametest.Starter039TestSupport; +import fr.koka.sanctuary.building.ColoredBricks; +import fr.koka.sanctuary.multiblock.Metatable; +import fr.koka.sanctuary.plans.*; +import java.io.*; +import java.nio.*; +import java.nio.file.*; +import java.util.*; +import java.util.concurrent.atomic.*; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; +import net.fabricmc.fabric.api.client.gametest.v1.FabricClientGameTest; +import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.components.events.*; +import net.minecraft.client.gui.screens.worldselection.WorldCreationUiState; +import net.minecraft.client.input.KeyEvent; +import net.minecraft.core.*; +import net.minecraft.network.chat.contents.TranslatableContents; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.item.ItemEntity; +import net.minecraft.world.inventory.ContainerInput; +import net.minecraft.world.item.*; +import net.minecraft.world.level.GameType; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.phys.*; + +/** Actual GLB texture/scene import, paid native crafting, placement, drops, reconnect and plan coexistence. */ +public final class Statuary106ClientChecks implements FabricClientGameTest { + private static final BlockPos WORKSHOP=new BlockPos(2,1,2),SCULPTURE=new BlockPos(2,1,0),TABLE=new BlockPos(-3,1,2); + private static final AtomicReference ORIGINAL=new AtomicReference<>(); + private static final AtomicReference MODEL=new AtomicReference<>(); + public void runTest(ClientGameTestContext c){ + var intro=new AtomicBoolean(true); + c.runOnClient(m->{m.options.renderDistance().set(6);m.options.simulationDistance().set(5);m.options.pauseOnLostFocus=false;IntroClient.setQuickTestIntro(true);}); + ClientTickEvents.END_CLIENT_TICK.register(m->{if(intro.get()&&m.gui.screen() instanceof HelloWorldScreen screen){ + Starter039TestSupport.selectFirst(screen);widgets(screen).filter(Button.class::isInstance).map(Button.class::cast).filter(b->b.getClass().getSimpleName().equals("ColorButton")).findFirst().orElseThrow().onPress(enter());button(screen,"sanctuary.progression.enter").onPress(enter());intro.set(false); + }}); + Path file=PlanClient.directory().resolve("stat106-cat-"+UUID.randomUUID()+".glb"); + net.fabricmc.fabric.api.client.gametest.v1.world.TestWorldSave saved; + try{ + System.out.println("STATUARY106_LIBRARY "+file);Files.createDirectories(file.getParent());byte[] fixture=fixture();Files.write(file,fixture); + var asset=GlbModel.load(file);var pixels=StatueVoxelizer.miniature(asset.faces());var source=VoxelSculpture.of(asset.id(),asset.name(),pixels); + MODEL.set(source.modelId());check(source.height()==16&&source.width()<16,"Proportions, 16 voxel longest axis");check(source.cells().stream().map(VoxelSculpture.Cell::color).distinct().count()>1,"Embedded PNG retains multiple colors"); + check(CreationLibrary.files(CreationLibrary.Kind.MODEL).contains(file)&&!CreationLibrary.files(CreationLibrary.Kind.SCHEMATIC).contains(file),"One folder, different filters"); + var invalid=fixture.clone();ByteBuffer.wrap(invalid).order(ByteOrder.LITTLE_ENDIAN).putInt(8,1);reject(invalid,"GLB header length"); + var oversize=new byte[GlbModel.MAX_BYTES+1];reject(oversize,"File bound"); + reject(edit(fixture,j->j.getAsJsonArray("buffers").get(0).getAsJsonObject().addProperty("uri","https://example.invalid/mesh.bin")),"External buffer"); + reject(edit(fixture,j->j.getAsJsonArray("images").get(0).getAsJsonObject().addProperty("uri","../../texture.png")),"External texture"); + reject(edit(fixture,j->j.getAsJsonArray("accessors").get(0).getAsJsonObject().addProperty("byteOffset",Integer.MAX_VALUE)),"Accessor overflow"); + reject(edit(fixture,j->{var a=new JsonArray();a.add(0);j.getAsJsonArray("nodes").get(0).getAsJsonObject().add("children",a);}),"Cyclic scene"); + var interop=net.fabricmc.loader.api.FabricLoader.getInstance().getGameDir().resolve("../../../../../build/statuary106-evidence/BoxTextured.glb").normalize(); + if(Files.exists(interop)){var box=GlbModel.read(interop);check(box.size()==12,"Independent Khronos indexed GLB");check(!StatueVoxelizer.miniature(box).isEmpty(),"Independent textured box voxelizes");System.out.println("STATUARY106_INTEROP_PASS Khronos BoxTextured GLB");} + check(source.rotate().rotate().rotate().rotate().equals(source),"Four rotations preserve ID and data"); + try{new VoxelSculpture(source.modelId(),"bad",16,16,16,List.of(new VoxelSculpture.Cell(0,0,0,-1),new VoxelSculpture.Cell(0,0,0,-1)));throw new AssertionError("Duplicate cell accepted");}catch(IllegalArgumentException expected){} + 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()){ + saved=world.getWorldSave();c.waitFor(m->ProgressionClient.active()&&m.player!=null&&m.gui.screen()==null&&!IntroSpawnFlash.active(),1200); + var server=world.getServer(); + server.runOnServer(s->{var p=player(s);p.teleportTo(.5,1,.5);p.getInventory().clearContent();p.setGameMode(GameType.SURVIVAL);for(int x=-4;x<=6;x++)for(int z=-2;z<=6;z++)p.level().setBlockAndUpdate(new BlockPos(x,0,z),Blocks.STONE.defaultBlockState());p.level().setBlockAndUpdate(WORKSHOP,Statuary.WORKSHOP.defaultBlockState()); + for(var q:Metatable.members(TABLE))p.level().setBlockAndUpdate(q,Blocks.CRAFTING_TABLE.defaultBlockState());check(Metatable.assemble(p,TABLE),"Metabli"); + s.getCommands().performPrefixedCommand(s.createCommandSourceStack(),"sanctuary time vanilla");s.getCommands().performPrefixedCommand(s.createCommandSourceStack(),"time set noon");}); + c.waitFor(m->m.level.getBlockState(WORKSHOP).is(Statuary.WORKSHOP)&&!m.player.isCreative(),200); + c.runOnClient(m->{m.options.guiScale().set(2);m.getWindow().setWindowed(1280,800);m.resizeGui();}); + open(c);c.runOnClient(m->((ClayWorkshopScreen)m.gui.screen()).onFilesDrop(List.of(file))); + c.waitFor(m->!((ClayWorkshopScreen)m.gui.screen()).busy()&&((ClayWorkshopScreen)m.gui.screen()).selected()!=null,200);c.waitTicks(10); + server.runOnServer(s->{var p=player(s);var menu=(ClayWorkshopMenu)p.containerMenu;check(menu.getSlot(1).getItem().isEmpty(),"No clay, no result");menu.getSlot(0).set(new ItemStack(Items.CLAY,3));menu.broadcastChanges();}); + c.waitFor(m->m.player.containerMenu.getSlot(1).getItem().is(Statuary.ITEM),100); + c.takeScreenshot("statuary106-workshop-en"); + server.runOnServer(s->{var menu=(ClayWorkshopMenu)player(s).containerMenu;ORIGINAL.set(StatuaryEntity.sculpture(menu.getSlot(1).getItem()));check(ORIGINAL.get().equals(source),"Plain clay keeps exact colors and model ID"); + for(var family:ColoredBricks.families()){var colored=ClayWorkshopMenu.material(source,new ItemStack(family.clay()));check(colored.modelId().equals(source.modelId())&&colored.cells().size()==source.cells().size()&&colored.cells().stream().map(VoxelSculpture.Cell::color).distinct().count()==1,"All 16 clays keep geometry and ID, recolor uniformly");}}); + c.runOnClient(m->m.gameMode.handleContainerInput(m.player.containerMenu.containerId,1,0,ContainerInput.PICKUP,m.player)); + c.waitFor(m->m.player.containerMenu.getCarried().is(Statuary.ITEM),100); + c.waitTicks(6); + server.runOnServer(s->{var menu=(ClayWorkshopMenu)player(s).containerMenu;check(menu.getSlot(0).getItem().getCount()==2,"Taking one statue costs exactly one clay");check(menu.getCarried().getCount()==1,"One portable item");}); + close(c);c.waitTicks(5); + server.runOnServer(s->{var p=player(s);check(count(p,Items.CLAY)==2,"Closing returns unused clay");equip(p,Statuary.ITEM);p.inventoryMenu.broadcastFullState();check(p.getMainHandItem().is(Statuary.ITEM),"Crafted statue equipped on server");}); + c.waitFor(m->m.player.getMainHandItem().is(Statuary.ITEM),100);place(c,SCULPTURE);c.waitTicks(6); + c.waitFor(m->m.level.getBlockEntity(SCULPTURE) instanceof StatuaryEntity e&&e.sculpture()!=null,100); + server.runOnServer(s->check(((StatuaryEntity)player(s).level().getBlockEntity(SCULPTURE)).sculpture().equals(source),"Native placement loads voxel data on server")); + c.getInput().lookAt(SCULPTURE);c.waitTicks(5);c.takeScreenshot("statuary106-world-colors"); + // Native block loot preserves the entire model. Re-place that same dropped item. + server.runOnServer(s->{var p=player(s);p.level().destroyBlock(SCULPTURE,true);var dropped=p.level().getEntitiesOfClass(ItemEntity.class,new AABB(SCULPTURE).inflate(1)).stream().filter(e->e.getItem().is(Statuary.ITEM)).findFirst().orElseThrow(); + check(StatuaryEntity.sculpture(dropped.getItem()).equals(source),"Native drop keeps name, ID and voxels");p.getInventory().setItem(p.getInventory().getSelectedSlot(),dropped.getItem().copy());dropped.discard();p.inventoryMenu.broadcastFullState();}); + c.waitFor(m->m.level.getBlockState(SCULPTURE).isAir()&&m.player.getMainHandItem().is(Statuary.ITEM),100);place(c,SCULPTURE);server.waitFor(s->player(s).level().getBlockEntity(SCULPTURE) instanceof StatuaryEntity e&&source.equals(e.sculpture()),100);c.waitFor(m->m.level.getBlockEntity(SCULPTURE) instanceof StatuaryEntity e&&source.equals(e.sculpture()),100); + // Reuse the same file with colored clay; server, rather than the client, chooses the tint. + server.runOnServer(s->player(s).getInventory().setItem(player(s).getInventory().getSelectedSlot(),ItemStack.EMPTY));c.waitTicks(4);open(c); + c.runOnClient(m->((ClayWorkshopScreen)m.gui.screen()).load(file));c.waitFor(m->!((ClayWorkshopScreen)m.gui.screen()).busy()&&((ClayWorkshopScreen)m.gui.screen()).selected()!=null,200);c.waitTicks(10); + server.runOnServer(s->{var menu=(ClayWorkshopMenu)player(s).containerMenu;var blue=ColoredBricks.families().stream().filter(f->f.color()==DyeColor.BLUE).findFirst().orElseThrow();menu.getSlot(0).set(new ItemStack(blue.clay(),2));menu.broadcastChanges();}); + c.waitFor(m->m.player.containerMenu.getSlot(1).getItem().is(Statuary.ITEM),100);language(c,"fr_fr");c.takeScreenshot("statuary106-workshop-fr-blue"); + server.runOnServer(s->{var menu=(ClayWorkshopMenu)player(s).containerMenu;var blue=StatuaryEntity.sculpture(menu.getSlot(1).getItem());check(blue.modelId().equals(MODEL.get())&&blue.cells().stream().allMatch(v->v.color()==0xFFA0A7BC),"Blue input overrides uploaded model colors");}); + c.runOnClient(m->m.gameMode.handleContainerInput(m.player.containerMenu.containerId,1,0,ContainerInput.QUICK_MOVE,m.player));c.waitTicks(10); + server.runOnServer(s->{var p=player(s);check(p.containerMenu.getSlot(0).getItem().isEmpty(),"Shift crafting consumes stock");check(count(p,Statuary.ITEM)==2,"Shift crafting produces exactly paid quantity");});close(c); + // At the Metabli, the same source creates a full-sized construction plan, with no placement in survival. + server.runOnServer(s->Metatable.open(player(s),TABLE));c.waitForScreen(PlansScreen.class); + c.runOnClient(m->{((PlansScreen)m.gui.screen()).onFilesDrop(List.of(file));button(m.gui.screen(),"sanctuary.plans.palette.concrete").onPress(enter());});c.takeScreenshot("statuary106-metabli-fr"); + c.runOnClient(m->button(m.gui.screen(),"sanctuary.plans.generate").onPress(enter()));c.waitFor(m->!PlanClient.busy&&PlanClient.plan!=null&&m.gui.screen()==null,400); + c.runOnClient(m->check(PlanClient.plan.height()==32&&PlanClient.plan.cells().size()>source.cells().size(),"Same source produces real-block statue at selected height")); + server.runOnServer(s->check(((StatuaryEntity)player(s).level().getBlockEntity(SCULPTURE)).sculpture().equals(source),"Plan generation leaves existing miniature intact")); + // Cancel an import by closing the native menu; late worker completion cannot craft in the next session. + c.runOnClient(m->PlanClient.clear());open(c);c.runOnClient(m->{((ClayWorkshopScreen)m.gui.screen()).load(file);m.gui.screen().onClose();});c.waitTicks(12); + server.runOnServer(s->check(player(s).containerMenu==player(s).inventoryMenu,"Closed workshop releases crafting session")); + check(Arrays.equals(Files.readAllBytes(file),fixture),"Original GLB never rewritten"); + } + Files.delete(file); // Placed models must survive loss of the source file. + try(var world=saved.open()){ + c.waitFor(m->m.player!=null&&m.gui.screen()==null,1200);var server=world.getServer(); + server.runOnServer(s->{var entity=(StatuaryEntity)player(s).level().getBlockEntity(SCULPTURE);check(entity.sculpture().equals(ORIGINAL.get()),"Server save and reload preserve source-independent voxels");check(count(player(s),Statuary.ITEM)==2,"Colored inventory statues survive reconnect");}); + c.waitFor(m->m.level.getBlockEntity(SCULPTURE) instanceof StatuaryEntity e&&e.sculpture()!=null,150);c.runOnClient(m->check(((StatuaryEntity)m.level.getBlockEntity(SCULPTURE)).sculpture().equals(ORIGINAL.get()),"Fresh client receives server model without file")); + c.getInput().lookAt(SCULPTURE);c.takeScreenshot("statuary106-reload-no-source"); + } + System.out.println("STATUARY106_PASS GLB textured scene, bounded import, shared folder filters, native paid crafting, 16 clay colors, model ID, shift-click, place/drop/re-place, Metabli plan, cancellation, server save/reconnect without source"); + }catch(IOException e){throw new AssertionError(e);}finally{try{Files.deleteIfExists(file);}catch(IOException ignored){}} + } + private static ServerPlayer player(net.minecraft.server.MinecraftServer s){return s.getPlayerList().getPlayers().getFirst();} + private static void close(ClientGameTestContext c){c.runOnClient(m->m.gui.screen().onClose());c.waitFor(m->m.gui.screen()==null,100);} + private static void open(ClientGameTestContext c){c.runOnClient(m->m.gameMode.useItemOn(m.player,InteractionHand.MAIN_HAND,new BlockHitResult(Vec3.atCenterOf(WORKSHOP),Direction.UP,WORKSHOP,false)));c.waitForScreen(ClayWorkshopScreen.class);} + private static void place(ClientGameTestContext c,BlockPos pos){ + c.runOnClient(m->m.player.lookAt(net.minecraft.commands.arguments.EntityAnchorArgument.Anchor.EYES,new Vec3(pos.getX()+.5,pos.getY()-.001,pos.getZ()+.5)));c.waitTicks(3); + c.runOnClient(m->check(m.hitResult instanceof BlockHitResult hit&&hit.getBlockPos().equals(pos.below())&&hit.getDirection()==Direction.UP,"Native cursor targets support top face")); + c.getInput().holdKey(o->o.keyUse);c.waitTicks(1);c.getInput().releaseKey(o->o.keyUse); + } + private static int count(ServerPlayer p,Item item){int n=0;for(int i=0;i{m.getLanguageManager().setSelected(lang);return m.reloadResourcePacks();});c.waitFor(m->reload.isDone()&&m.gui.overlay()==null,1200);reload.join();} + private static KeyEvent enter(){return new KeyEvent(InputConstants.KEY_RETURN,0,0);} + private static java.util.stream.Stream widgets(GuiEventListener root){return java.util.stream.Stream.concat(java.util.stream.Stream.of(root),root instanceof ContainerEventHandler container?container.children().stream().flatMap(Statuary106ClientChecks::widgets):java.util.stream.Stream.empty());} + private static Button button(GuiEventListener root,String key){return widgets(root).filter(Button.class::isInstance).map(Button.class::cast).filter(b->b.getMessage().getContents() instanceof TranslatableContents t&&t.getKey().equals(key)).findFirst().orElseThrow(()->new AssertionError("Missing button "+key));} + private static void check(boolean ok,String message){if(!ok)throw new AssertionError(message);} + private static void reject(byte[] bytes,String label)throws IOException{try{GlbModel.read(bytes);throw new AssertionError("Accepted "+label);}catch(IOException expected){}} + private static byte[] edit(byte[] file,java.util.function.Consumer edit){ + var input=ByteBuffer.wrap(file).order(ByteOrder.LITTLE_ENDIAN);int length=input.getInt(12);byte[] original=Arrays.copyOfRange(file,20,20+length);var json=JsonParser.parseString(new String(original,java.nio.charset.StandardCharsets.UTF_8)).getAsJsonObject();edit.accept(json); + byte[] text=json.toString().getBytes(java.nio.charset.StandardCharsets.UTF_8);int padded=(text.length+3)/4*4;byte[] binary=Arrays.copyOfRange(file,20+length,file.length);var out=ByteBuffer.allocate(20+padded+binary.length).order(ByteOrder.LITTLE_ENDIAN);out.putInt(0x46546C67).putInt(2).putInt(out.capacity()).putInt(padded).putInt(0x4E4F534A).put(text);while(out.position()<20+padded)out.put((byte)' ');out.put(binary);return out.array(); + } + /** A tiny authored cat built from triangle boxes, embedded 2×2 texture and a translated scene node. */ + private static byte[] fixture()throws IOException{ + var xyz=new ArrayList();var uv=new ArrayList(); + for(float[] box:List.of(new float[]{.2f,0,.1f,.8f,1,.65f},new float[]{.1f,1,.05f,.9f,1.7f,.7f},new float[]{.1f,1.7f,.1f,.3f,2.1f,.5f},new float[]{.7f,1.7f,.1f,.9f,2.1f,.5f},new float[]{.75f,.1f,.2f,1.1f,.65f,.5f})){ + float x=box[0],y=box[1],z=box[2],X=box[3],Y=box[4],Z=box[5];float[][] v={{x,y,z},{X,y,z},{X,Y,z},{x,Y,z},{x,y,Z},{X,y,Z},{X,Y,Z},{x,Y,Z}}; + int[][] quads={{0,3,2,1},{5,6,7,4},{4,7,3,0},{1,2,6,5},{3,7,6,2},{4,0,1,5}}; + for(var q:quads)for(int i:new int[]{0,1,2,0,2,3}){for(float f:v[q[i]])xyz.add(f);uv.add(i==0||i==1?0f:1f);uv.add(i==0||i==3?1f:0f);} + } + var image=new java.awt.image.BufferedImage(2,2,java.awt.image.BufferedImage.TYPE_INT_ARGB);image.setRGB(0,0,0xFFFF9933);image.setRGB(1,0,0xFF44CCAA);image.setRGB(0,1,0xFF4477EE);image.setRGB(1,1,0xFFFFFFEE);var png=new ByteArrayOutputStream();javax.imageio.ImageIO.write(image,"png",png); + int pbytes=xyz.size()*4,ubytes=uv.size()*4,ibytes=png.size(),total=pbytes+ubytes+ibytes;var binary=ByteBuffer.allocate((total+3)/4*4).order(ByteOrder.LITTLE_ENDIAN);for(float f:xyz)binary.putFloat(f);for(float f:uv)binary.putFloat(f);binary.put(png.toByteArray()); + String json=""" + {"asset":{"version":"2.0"},"scene":0,"scenes":[{"nodes":[0]}],"nodes":[{"mesh":0,"translation":[3,2,-1]}], + "meshes":[{"primitives":[{"attributes":{"POSITION":0,"TEXCOORD_0":1},"material":0}]}], + "materials":[{"pbrMetallicRoughness":{"baseColorTexture":{"index":0}}}],"textures":[{"source":0}],"images":[{"mimeType":"image/png","bufferView":2}], + "buffers":[{"byteLength":%d}],"bufferViews":[{"buffer":0,"byteOffset":0,"byteLength":%d},{"buffer":0,"byteOffset":%d,"byteLength":%d},{"buffer":0,"byteOffset":%d,"byteLength":%d}], + "accessors":[{"bufferView":0,"componentType":5126,"count":%d,"type":"VEC3"},{"bufferView":1,"componentType":5126,"count":%d,"type":"VEC2"}]} + """.formatted(total,pbytes,pbytes,ubytes,pbytes+ubytes,ibytes,xyz.size()/3,uv.size()/2); + byte[] text=json.getBytes(java.nio.charset.StandardCharsets.UTF_8);int jl=(text.length+3)/4*4;var out=ByteBuffer.allocate(28+jl+binary.capacity()).order(ByteOrder.LITTLE_ENDIAN);out.putInt(0x46546C67).putInt(2).putInt(out.capacity()).putInt(jl).putInt(0x4E4F534A).put(text);while(out.position()<20+jl)out.put((byte)' ');out.putInt(binary.capacity()).putInt(0x004E4942).put(binary.array());return out.array(); + } +} diff --git a/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/Temple097ClientChecks.java b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/Temple097ClientChecks.java new file mode 100644 index 0000000..7b9bca7 --- /dev/null +++ b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/Temple097ClientChecks.java @@ -0,0 +1,111 @@ +package fr.koka.sanctuary.client; + +import fr.koka.sanctuary.worldgen.SanctuaryChunkGenerator; +import fr.koka.sanctuary.expansion.*; +import java.util.*; +import java.util.concurrent.atomic.AtomicReference; +import net.fabricmc.fabric.api.client.gametest.v1.FabricClientGameTest; +import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext; +import net.fabricmc.fabric.api.client.gametest.v1.world.TestWorldSave; +import net.minecraft.client.gui.screens.worldselection.WorldCreationUiState; +import net.minecraft.core.*; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.levelgen.structure.*; +import net.minecraft.world.level.levelgen.structure.pieces.StructurePieceSerializationContext; + +/** Real failing seed, native loot and traps, and cache/chunk save reload. */ +public final class Temple097ClientChecks implements FabricClientGameTest { + private static final AtomicReference HALL=new AtomicReference<>(); + public void runTest(ClientGameTestContext c) { + System.setProperty("sanctuary.expansion.structureDiagnostics","true"); + var lastHello=new AtomicReference(); + net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents.END_CLIENT_TICK.register(m->{ + if(m.gui.screen() instanceof HelloWorldScreen screen&&lastHello.getAndSet(screen)!=screen){ + IntroClient.setQuickTestIntro(true);fr.koka.sanctuary.gametest.Starter039TestSupport.selectFirst(screen); + screen.children().stream().filter(net.minecraft.client.gui.components.Button.class::isInstance).map(net.minecraft.client.gui.components.Button.class::cast).filter(b->b.getClass().getSimpleName().equals("ColorButton")).findFirst().orElseThrow().onPress(new net.minecraft.client.input.KeyEvent(com.mojang.blaze3d.platform.InputConstants.KEY_RETURN,0,0)); + screen.children().stream().filter(net.minecraft.client.gui.components.Button.class::isInstance).map(net.minecraft.client.gui.components.Button.class::cast).filter(b->b.getMessage().getContents() instanceof net.minecraft.network.chat.contents.TranslatableContents t&&t.getKey().equals("sanctuary.progression.enter")).findFirst().orElseThrow().onPress(new net.minecraft.client.input.KeyEvent(com.mojang.blaze3d.platform.InputConstants.KEY_RETURN,0,0)); + } + }); + c.runOnClient(m->{m.options.renderDistance().set(4);m.options.simulationDistance().set(5);m.options.pauseOnLostFocus=false;}); + var baseline=new AtomicReference>();TestWorldSave save; + try(var world=c.worldBuilder().adjustSettings(s->{s.setName("Temple097 "+UUID.randomUUID());s.setSeed("-4700804240597771092");s.setAllowCommands(true);s.setGenerateStructures(true);s.setGameMode(WorldCreationUiState.SelectedGameMode.CREATIVE);s.setWorldType(s.getNormalPresetList().stream().filter(e->e.preset()!=null&&e.preset().getRegisteredName().equals("sanctuary:sanctuary")).findFirst().orElseThrow());}).create()) { + save=world.getWorldSave(); + world.getServer().runOnServer(s->baseline.set(inspect(s.overworld()))); + 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 at=HALL.get();p.setGameMode(net.minecraft.world.level.GameType.CREATIVE);p.setNoGravity(true);p.getAbilities().flying=true;p.onUpdateAbilities();p.teleportTo(at.getX()+1.5,at.getY()+3,at.getZ()-5);}); + c.waitFor(m->m.player.distanceToSqr(HALL.get().getX()+1.5,HALL.get().getY()+3,HALL.get().getZ()-5)<2,200); + c.getInput().lookAt(HALL.get().offset(1,0,1));c.waitTicks(25);c.takeScreenshot("catalog099-hall-metatable"); + } + try(var world=save.open()) { + world.getServer().runOnServer(s->{check(baseline.get().equals(inspect(s.overworld())),"Native structures and loot survive reload");var g=(SanctuaryChunkGenerator)s.overworld().getChunkSource().getGenerator();check(g.planCache().hits()>0,"Reopen restores native planning cache");}); + } + System.out.println("TEMPLE097_PASS: supplied seed, two complete native temples, native starts, loot and traps, metadata round-trip, locate, chunk/cache reload"); + } + private static void hall099(ServerLevel level,SanctuaryChunkGenerator host){ + var hall=host.undergroundHall22().networks().stream().flatMap(n->n.modules().stream()).filter(m->m.kind()==fr.koka.sanctuary.worldgen.city.LostCityUnderground16.Kind.EXPANSION_HALL).findFirst().orElseThrow(); + var origin=new BlockPos(hall.minX()+hall.width()/2-1,hall.minY()+3,hall.minZ()+hall.depth()/2-1); + for(var pos:fr.koka.sanctuary.multiblock.Metatable.members(origin))level.getChunkAt(pos); + HALL.set(origin); + System.out.println("HALL099 states="+fr.koka.sanctuary.multiblock.Metatable.members(origin).stream().map(level::getBlockState).toList()); + check(fr.koka.sanctuary.multiblock.Metatable.state(level,origin,true)==1,"Generated hall contains an assembled central Metatable"); + for(var pos:fr.koka.sanctuary.multiblock.Metatable.members(origin))check(level.getBlockState(pos.below()).is(Blocks.SMOOTH_STONE),"Metatable rests on existing raised plinth"); + var codec=StructurePieceSerializationContext.fromLevel(level); + var modern=new fr.koka.sanctuary.worldgen.city.LostCityUndergroundPiece20(hall,true);var tag=modern.createTag(codec); + check(tag.getBooleanOr("SanctuaryMetatable099",false),"New hall piece persists opt-in"); + var restored=new fr.koka.sanctuary.worldgen.city.LostCityUndergroundPiece20(codec,tag); + check(restored.createTag(codec).equals(tag),"New hall piece round-trips"); + tag.remove("SanctuaryMetatable099");var historical=new fr.koka.sanctuary.worldgen.city.LostCityUndergroundPiece20(codec,tag); + check(!historical.createTag(codec).contains("SanctuaryMetatable099"),"Historical unmarked pieces never opt into new furniture"); + System.out.println("HALL099_PASS: assembled Metatable on central plinth at "+origin+", native saved opt-in, historical absence preserved"); + } + private static List inspect(ServerLevel level) { + var host=(SanctuaryChunkGenerator)level.getChunkSource().getGenerator(); + check(host.expeditionTemples002().size()==2,"Both expedition temples required");var evidence=new ArrayList(); + hall099(level,host); + for(var temple:host.expeditionTemples002()) { + var planned=temple.start();var cp=planned.getChunkPos();var box=ExpeditionTemples002.footprint(planned); + for(int x=box.minX()>>4;x<=box.maxX()>>4;x++)for(int z=box.minZ()>>4;z<=box.maxZ()>>4;z++)level.getChunk(x,z); + var actual=level.structureManager().getStartForStructure(temple.structure().value(),level.getChunk(cp.x(),cp.z())); + check(actual!=null&&actual.isValid(),"Native start exists"); + var codec=StructurePieceSerializationContext.fromLevel(level);var nbt=actual.createTag(codec,cp);var restored=StructureStart.loadStaticStart(codec,nbt,level.getSeed()); + check(restored!=null&&restored.createTag(codec,cp).equals(nbt),"NBT round trip includes optional foundation metadata"); + int chests=0,tnt=0,dispensers=0,masonry=0; + for(var at:BlockPos.betweenClosed(box.minX(),box.minY(),box.minZ(),box.maxX(),box.maxY(),box.maxZ())) { + var block=level.getBlockState(at);if(block.is(Blocks.CHEST))chests++;if(block.is(Blocks.TNT))tnt++;if(block.is(Blocks.DISPENSER))dispensers++; + if(block.is(Blocks.SANDSTONE)||block.is(Blocks.COBBLESTONE)||block.is(Blocks.MOSSY_COBBLESTONE))masonry++; + } + foundationChecks(level,actual); + var id=temple.structure().unwrapKey().orElseThrow().identifier().toString();boolean desert=id.endsWith("desert_pyramid"); + check(chests>=(desert?4:2)&&masonry>100,"Temple masonry and chests: "+id+" "+chests+" "+masonry); + check(desert?tnt==9:dispensers==2,"Native traps: "+id+" TNT="+tnt+" dispensers="+dispensers); + check(host.findNearestMapStructure(level,HolderSet.direct(temple.structure()),actual.getBoundingBox().getCenter(),1,false)!=null,"Locate finds temple"); + String row=id+" "+cp+" "+box+" chests="+chests+" TNT="+tnt+" dispensers="+dispensers+" masonry="+masonry; + evidence.add(row);System.out.println("TEMPLE097 "+row+" metadata="+((NativeStructureMetadata23)(Object)actual).sanctuary$native23()); + } + return List.copyOf(evidence); + } + static void foundationChecks(ServerLevel level,StructureStart original) { + var codec=StructurePieceSerializationContext.fromLevel(level);var at=original.getChunkPos(); + var copy=StructureStart.loadStaticStart(codec,original.createTag(codec,at),level.getSeed()); + var old=((NativeStructureMetadata23)(Object)copy).sanctuary$native23();var box=copy.getPieces().getFirst().getBoundingBox();int bottom=box.minY()-16; + ((NativeStructureMetadata23)(Object)copy).sanctuary$native23(new NativeStructureMetadata23.Data(old.islandSeed(),old.seaLevel(),bottom)); + var encoded=copy.createTag(codec,at);var restored=StructureStart.loadStaticStart(codec,encoded,level.getSeed()); + check(((NativeStructureMetadata23)(Object)restored).sanctuary$native23().templeFoundationMinY()==bottom,"Prepared foundation survives native NBT"); + var writes=new HashMap();var clipping=new AtomicReference(); + var region=(net.minecraft.world.level.WorldGenLevel)java.lang.reflect.Proxy.newProxyInstance(getClassLoader(),new Class[]{net.minecraft.world.level.WorldGenLevel.class},(proxy,method,args)->switch(method.getName()) { + case "getMinY" -> level.getMinY();case "getMaxY" -> level.getMaxY(); + case "setBlock" -> {var pos=((BlockPos)args[0]).immutable();check(clipping.get().isInside(pos),"Foundation never writes outside current decorating chunk");writes.put(pos,(net.minecraft.world.level.block.state.BlockState)args[1]);yield true;} + default -> throw new AssertionError("Unexpected world access: "+method.getName()); + }); + for(int x=box.minX()>>4;x<=box.maxX()>>4;x++)for(int z=box.minZ()>>4;z<=box.maxZ()>>4;z++) { + var bounds=new BoundingBox(x*16,level.getMinY(),z*16,x*16+15,level.getMaxY()-1,z*16+15);clipping.set(bounds);ExpeditionTemples002.prepareFoundation(region,restored,bounds); + } + check(writes.size()==box.getXSpan()*box.getZSpan()*16,"Continuous foundation under the entire native footprint"); + check(writes.keySet().stream().allMatch(p->p.getY()>=bottom&&p.getY(){ + if(m.gui.screen() instanceof IntroScreen s)s.onClose(); + if(intro.get()&&m.gui.screen() instanceof HelloWorldScreen s){Starter039TestSupport.selectFirst(s); + s.children().stream().filter(Button.class::isInstance).map(Button.class::cast).filter(b->b.getClass().getSimpleName().equals("ColorButton")).findFirst().orElseThrow().onPress(enter()); + s.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("Tooltip093 "+UUID.randomUUID());s.setSeed("42");s.setGameMode(WorldCreationUiState.SelectedGameMode.SURVIVAL);s.setWorldType(s.getNormalPresetList().stream().filter(e->e.preset()!=null&&e.preset().getRegisteredName().equals("sanctuary_test:flat")).findFirst().orElseThrow());}).create()){ + world.getConnection().waitForChunksDownload(2400);c.waitFor(m->m.player!=null&&ProgressionClient.active()&&m.gui.screen()==null,1200); + world.getServer().runOnServer(s->s.getPlayerList().getPlayers().getFirst().setGameMode(GameType.CREATIVE)); + var eggs=c.computeOnClient(m->BuiltInRegistries.ITEM.stream().filter(SpawnEggItem.class::isInstance).map(ItemStack::new).filter(e->CompanionTooltips.type(e)!=null).toList()); + check(eggs.size()==88,"All 88 familiar eggs covered");Tooltip093Probe.armed=true; + for(String locale:List.of("fr_fr","en_us")){ + var reload=c.computeOnClient(m->{m.getLanguageManager().setSelected(locale);m.options.languageCode=locale;return m.reloadResourcePacks();});c.waitFor(m->reload.isDone()&&m.gui.overlay()==null,1200);reload.join(); + for(boolean details:new boolean[]{false,true}){ + c.runOnClient(m->{ProgressionClient.operator=false;CompanionClient.studied=details;}); + var context=c.computeOnClient(m->Item.TooltipContext.of(m.level)); + var player=c.computeOnClient(m->m.player); + var async=CompletableFuture.supplyAsync(()->eggs.stream().map(e->e.getTooltipLines(context,player,TooltipFlag.NORMAL)).toList()); + c.waitFor(m->async.isDone(),300);var workerLines=async.join(); + c.runOnClient(m->{for(int i=0;i2,"Familiar description is present"); + check(visible.stream().noneMatch(t->t.getString().startsWith("sanctuary.")),"Descriptions remain translated"); + }}); + // Exactly the native asynchronous path from the supplied crash, without joining on the render thread. + var search=c.computeOnClient(m->{var trees=new SessionSearchTrees();trees.updateCreativeTooltips(m.level.registryAccess(),eggs);return trees;}); + var indexed=CompletableFuture.supplyAsync(search::creativeNameSearch);c.waitFor(m->indexed.isDone(),300);var tree=indexed.join(); + String query=c.computeOnClient(m->eggs.getFirst().getHoverName().getString().toLowerCase(Locale.ROOT)); + check(!tree.search(query).isEmpty(),"Native creative search resolves the indexed egg"); + c.runOnClient(m->search.rebuildAfterLanguageChange());var again=CompletableFuture.supplyAsync(search::creativeNameSearch);c.waitFor(m->again.isDone(),300);check(!again.join().search(query).isEmpty(),"Language rebuild retains indexed results"); + System.out.println("TOOLTIP093_INDEX_PASS locale="+locale+" details="+details); + } + c.runOnClient(m->m.gui.setScreen(new CreativeModeInventoryScreen(m.player,m.level.enabledFeatures(),false)));c.waitTicks(8); + // Native creative inventory selects the search tab when its search key is pressed. + c.getInput().pressKey(o->o.keyChat);c.waitTicks(4); + c.getInput().typeChars("w");c.waitTicks(5);c.getInput().typeChars("olf");c.waitTicks(5); + c.runOnClient(m->check(m.gui.screen() instanceof CreativeModeInventoryScreen,"Typing in creative inventory remains usable")); + c.takeScreenshot("tooltip093-creative-search-"+locale);c.runOnClient(m->m.gui.setScreen(null)); + } + System.out.println("TOOLTIP093_PASS: worker tooltips and native search/rebuild, all 88 eggs, cryptic/detailed FR/EN, resource reload and creative typing"); + }finally{Tooltip093Probe.armed=false;intro.set(false);} + } + private static String text(List lines){return lines.stream().map(Component::getString).reduce("",String::concat).replaceAll("\\s+","");} + private static void check(boolean v,String message){if(!v)throw new AssertionError(message);} + private static KeyEvent enter(){return new KeyEvent(InputConstants.KEY_RETURN,0,0);} +} diff --git a/mods/sanctuary/src/gametest/java/fr/koka/sanctuary/gametest/Fourneau094Probe.java b/mods/sanctuary/src/gametest/java/fr/koka/sanctuary/gametest/Fourneau094Probe.java new file mode 100644 index 0000000..41f4a77 --- /dev/null +++ b/mods/sanctuary/src/gametest/java/fr/koka/sanctuary/gametest/Fourneau094Probe.java @@ -0,0 +1,15 @@ +package fr.koka.sanctuary.gametest; + +import java.util.ArrayList; +import java.util.List; +import net.minecraft.core.particles.ParticleOptions; +import net.minecraft.world.phys.Vec3; + +public final class Fourneau094Probe { + public record Emission(ParticleOptions type,Vec3 point){} + public static boolean armed; + public static final List emissions=new ArrayList<>(); + public static void record(ParticleOptions type,double x,double y,double z){ + if(armed)emissions.add(new Emission(type,new Vec3(x,y,z))); + } +} diff --git a/mods/sanctuary/src/gametest/java/fr/koka/sanctuary/gametest/Tooltip093Probe.java b/mods/sanctuary/src/gametest/java/fr/koka/sanctuary/gametest/Tooltip093Probe.java new file mode 100644 index 0000000..79b12cd --- /dev/null +++ b/mods/sanctuary/src/gametest/java/fr/koka/sanctuary/gametest/Tooltip093Probe.java @@ -0,0 +1,11 @@ +package fr.koka.sanctuary.gametest; + +/** Test-only guard: even a warm font cache must never conceal off-thread glyph access. */ +public final class Tooltip093Probe { + public static volatile boolean armed; + private Tooltip093Probe() {} + public static void fontAccess(){ + if(armed&&!net.minecraft.client.Minecraft.getInstance().isSameThread()) + throw new AssertionError("TOOLTIP093_OFF_THREAD_FONT: tooltip indexing accessed the rendering font"); + } +} diff --git a/mods/sanctuary/src/gametest/java/fr/koka/sanctuary/gametest/mixin/Fourneau094ParticleProbeMixin.java b/mods/sanctuary/src/gametest/java/fr/koka/sanctuary/gametest/mixin/Fourneau094ParticleProbeMixin.java new file mode 100644 index 0000000..9c08b6f --- /dev/null +++ b/mods/sanctuary/src/gametest/java/fr/koka/sanctuary/gametest/mixin/Fourneau094ParticleProbeMixin.java @@ -0,0 +1,17 @@ +package fr.koka.sanctuary.gametest.mixin; + +import fr.koka.sanctuary.gametest.Fourneau094Probe; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.core.particles.ParticleOptions; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(ClientLevel.class) +public abstract class Fourneau094ParticleProbeMixin { + @Inject(method="addParticle(Lnet/minecraft/core/particles/ParticleOptions;DDDDDD)V",at=@At("HEAD")) + private void sanctuary$particle(ParticleOptions type,double x,double y,double z,double vx,double vy,double vz,CallbackInfo ci){ + Fourneau094Probe.record(type,x,y,z); + } +} diff --git a/mods/sanctuary/src/gametest/java/fr/koka/sanctuary/gametest/mixin/Tooltip093FontProbeMixin.java b/mods/sanctuary/src/gametest/java/fr/koka/sanctuary/gametest/mixin/Tooltip093FontProbeMixin.java new file mode 100644 index 0000000..9250bc6 --- /dev/null +++ b/mods/sanctuary/src/gametest/java/fr/koka/sanctuary/gametest/mixin/Tooltip093FontProbeMixin.java @@ -0,0 +1,13 @@ +package fr.koka.sanctuary.gametest.mixin; + +import fr.koka.sanctuary.gametest.Tooltip093Probe; +import net.minecraft.client.gui.Font; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.*; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(Font.class) +abstract class Tooltip093FontProbeMixin { + @Inject(method={"getSplitter","width"},at=@At("HEAD")) + private void verifyThread(CallbackInfoReturnable ci){Tooltip093Probe.fontAccess();} +} diff --git a/mods/sanctuary/src/gametest/resources/sanctuary-gametest.mixins.json b/mods/sanctuary/src/gametest/resources/sanctuary-gametest.mixins.json index 559f21e..49a258f 100644 --- a/mods/sanctuary/src/gametest/resources/sanctuary-gametest.mixins.json +++ b/mods/sanctuary/src/gametest/resources/sanctuary-gametest.mixins.json @@ -10,6 +10,8 @@ "defaultRequire": 1 }, "client": [ + "Fourneau094ParticleProbeMixin", + "Tooltip093FontProbeMixin", "ClientVsync046Mixin", "Inventory012RenderProbeMixin", "Atlas026ScreenProbeMixin", diff --git a/mods/sanctuary/src/main/java/fr/koka/sanctuary/SanctuaryMod.java b/mods/sanctuary/src/main/java/fr/koka/sanctuary/SanctuaryMod.java index f698ace..02adc72 100644 --- a/mods/sanctuary/src/main/java/fr/koka/sanctuary/SanctuaryMod.java +++ b/mods/sanctuary/src/main/java/fr/koka/sanctuary/SanctuaryMod.java @@ -43,6 +43,7 @@ public final class SanctuaryMod implements ModInitializer { @Override public void onInitialize() { + fr.koka.sanctuary.building.ColoredBricks.register(); fr.koka.sanctuary.sky.VortexClouds.register(); fr.koka.sanctuary.loading.PreloadProgress.register(); fr.koka.sanctuary.progression.SanctuaryGameplay.register(); @@ -70,6 +71,7 @@ public final class SanctuaryMod implements ModInitializer { fr.koka.sanctuary.realtime.RealtimeService.register(); fr.koka.sanctuary.weather.WeatherService.register(); fr.koka.sanctuary.plans.PlanService.register(); + fr.koka.sanctuary.plans.Statuary.register(); fr.koka.sanctuary.food.FoodSync.register(); fr.koka.sanctuary.food.FoodKnowledgeService.register(); fr.koka.sanctuary.grave.GraveService.register(); diff --git a/mods/sanctuary/src/main/java/fr/koka/sanctuary/building/ColoredBricks.java b/mods/sanctuary/src/main/java/fr/koka/sanctuary/building/ColoredBricks.java new file mode 100644 index 0000000..0f8ed5d --- /dev/null +++ b/mods/sanctuary/src/main/java/fr/koka/sanctuary/building/ColoredBricks.java @@ -0,0 +1,56 @@ +package fr.koka.sanctuary.building; + +import fr.koka.sanctuary.SanctuaryMod; +import java.util.*; +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.world.item.*; +import net.minecraft.world.level.block.*; +import net.minecraft.world.level.block.state.BlockBehaviour; + +/** Native building blocks; no world-generation, ticking entities or custom inventory. */ +public final class ColoredBricks { + public record Family(DyeColor color,Block bricks,Block slab,Block stairs,Block clay,Item clayBall,Item brick){} + private static final List FAMILIES=new ArrayList<>(); + // The order used by the native 26.3 Colored Blocks tab, rather than dye numeric IDs. + private static final List CREATIVE_COLORS=List.of( + DyeColor.WHITE,DyeColor.LIGHT_GRAY,DyeColor.GRAY,DyeColor.BLACK, + DyeColor.BROWN,DyeColor.RED,DyeColor.ORANGE,DyeColor.YELLOW, + DyeColor.LIME,DyeColor.GREEN,DyeColor.CYAN,DyeColor.LIGHT_BLUE, + DyeColor.BLUE,DyeColor.PURPLE,DyeColor.MAGENTA,DyeColor.PINK); + public static List families(){return List.copyOf(FAMILIES);} + public static void register(){ + for(var color:DyeColor.values()){ + String name=color.getName(); + var bricks=block(name+"_bricks",new Block(properties(name+"_bricks",Blocks.BRICKS,color))); + var slab=block(name+"_brick_slab",new SlabBlock(properties(name+"_brick_slab",Blocks.BRICK_SLAB,color))); + var stairs=block(name+"_brick_stairs",new Stairs(bricks,properties(name+"_brick_stairs",Blocks.BRICK_STAIRS,color))); + var clay=block(name+"_clay",new Block(properties(name+"_clay",Blocks.CLAY,color))); + var clayBall=item(name+"_clay_ball");var brick=item(name+"_brick"); + FAMILIES.add(new Family(color,bricks,slab,stairs,clay,clayBall,brick)); + } + var display=FAMILIES.stream().sorted(Comparator.comparingInt(f->CREATIVE_COLORS.indexOf(f.color()))).toList(); + CreativeModeTabEvents.modifyOutputEvent(CreativeModeTabs.COLORED_BLOCKS).register(out->{ + for(var f:display)out.accept(f.bricks); + for(var f:display)out.accept(f.stairs); + for(var f:display)out.accept(f.slab); + for(var f:display)out.accept(f.clay); + }); + CreativeModeTabEvents.modifyOutputEvent(CreativeModeTabs.INGREDIENTS).register(out->{ + for(var f:display)out.accept(f.clayBall); + for(var f:display)out.accept(f.brick); + }); + } + private static BlockBehaviour.Properties properties(String id,Block source,DyeColor color){ + return BlockBehaviour.Properties.ofFullCopy(source).mapColor(color.getMapColor()).setId(ResourceKey.create(Registries.BLOCK,SanctuaryMod.id(id))); + } + private static Block block(String name,Block block){ + var id=SanctuaryMod.id(name);Registry.register(BuiltInRegistries.BLOCK,id,block); + Registry.register(BuiltInRegistries.ITEM,id,new BlockItem(block,new Item.Properties().setId(ResourceKey.create(Registries.ITEM,id)).useBlockDescriptionPrefix()));return block; + } + private static Item item(String name){var id=SanctuaryMod.id(name);return Registry.register(BuiltInRegistries.ITEM,id,new Item(new Item.Properties().setId(ResourceKey.create(Registries.ITEM,id))));} + private static final class Stairs extends StairBlock {Stairs(Block base,BlockBehaviour.Properties properties){super(base.defaultBlockState(),properties);}} + private ColoredBricks(){} +} diff --git a/mods/sanctuary/src/main/java/fr/koka/sanctuary/client/ClayWorkshopScreen.java b/mods/sanctuary/src/main/java/fr/koka/sanctuary/client/ClayWorkshopScreen.java new file mode 100644 index 0000000..1c7dc5d --- /dev/null +++ b/mods/sanctuary/src/main/java/fr/koka/sanctuary/client/ClayWorkshopScreen.java @@ -0,0 +1,81 @@ +package fr.koka.sanctuary.client; + +import fr.koka.sanctuary.plans.*; +import java.nio.file.*; +import java.util.*; +import java.util.concurrent.*; +import com.mojang.blaze3d.Blaze3D; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; +import net.minecraft.client.gui.GuiGraphicsExtractor; +import net.minecraft.client.gui.components.*; +import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; +import net.minecraft.client.renderer.RenderPipelines; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.Identifier; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.item.ItemStack; + +/** File selection is local; the two ordinary inventory slots and every crafted item are server-owned. */ +public final class ClayWorkshopScreen extends AbstractContainerScreen { + private static final ExecutorService WORKER=Executors.newSingleThreadExecutor(r->{var t=new Thread(r,"Sanctuary clay models");t.setDaemon(true);return t;}); + private List files=List.of();private int page,generation,ticks;private boolean busy,pending; + private VoxelSculpture source,previewSource;private ItemStack preview=ItemStack.EMPTY,lastClay=ItemStack.EMPTY; + private Component status=text("choose_model");private Button rotate; + public ClayWorkshopScreen(ClayWorkshopMenu menu,Inventory inv,Component title){super(menu,inv,title,310,242);inventoryLabelX=138;inventoryLabelY=130;} + public static Component text(String key,Object...args){return Component.translatable("sanctuary.statuary."+key,args);} + public static void register(){ + net.minecraft.client.gui.screens.MenuScreens.register(Statuary.MENU,ClayWorkshopScreen::new);StatuaryRenderer.register(); + net.fabricmc.fabric.api.client.item.v1.ItemTooltipCallback.EVENT.register((stack,context,flags,lines)->{ + if(!stack.is(Statuary.ITEM))return;var sculpture=StatuaryEntity.sculpture(stack);if(sculpture!=null){lines.add(text("model_name",sculpture.name()));if(flags.isAdvanced())lines.add(Component.literal(sculpture.modelId()));} + }); + } + @Override protected void init(){ + super.init(); + try{files=CreationLibrary.files(CreationLibrary.Kind.MODEL);}catch(Exception e){status=ModelImportErrors.message(e);} + page=Math.min(page,Math.max(0,(files.size()-1)/4)); + for(int i=0;i<4;i++){int at=page*4+i;if(at>=files.size())break;var file=files.get(at); + var label=Component.literal(file.getFileName().toString());var b=addRenderableWidget(Button.builder(label,p->load(file)).bounds(leftPos+10,topPos+29+i*18,128,17).build());b.setTooltip(Tooltip.create(label));} + addRenderableWidget(Button.builder(Component.literal("←"),b->{page--;rebuildWidgets();}).bounds(leftPos+10,topPos+103,22,18).build()).active=page>0; + addRenderableWidget(Button.builder(text("folder"),b->folder()).bounds(leftPos+35,topPos+103,78,18).build()); + addRenderableWidget(Button.builder(Component.literal("→"),b->{page++;rebuildWidgets();}).bounds(leftPos+116,topPos+103,22,18).build()).active=(page+1)*4rebuildWidgets()).bounds(leftPos+10,topPos+123,128,18).build()); + rotate=addRenderableWidget(Button.builder(text("rotate"),b->{if(source!=null){source=source.rotate();pending=true;}}).bounds(leftPos+215,topPos+112,92,18).build());rotate.active=source!=null&&!busy; + } + private void folder(){try{Files.createDirectories(CreationLibrary.directory());Blaze3D.openPath(CreationLibrary.directory());}catch(Exception e){status=ModelImportErrors.message(e);}} + public void load(Path file){ + if(busy)return;busy=true;status=text("working");int token=++generation; + CompletableFuture.supplyAsync(()->{try{var asset=GlbModel.load(file);return VoxelSculpture.of(asset.id(),asset.name(),StatueVoxelizer.miniature(asset.faces()));}catch(Exception e){throw new CompletionException(e);}},WORKER) + .whenComplete((sculpture,error)->net.minecraft.client.Minecraft.getInstance().execute(()->{ + if(token!=generation||minecraft.gui.screen()!=this||minecraft.player==null||minecraft.player.containerMenu!=menu)return; + busy=false;if(error!=null){status=ModelImportErrors.message(error.getCause());return;} + source=sculpture;pending=true;status=text("ready",sculpture.name()); + })); + } + @Override protected void containerTick(){ + super.containerTick();rotate.active=source!=null&&!busy; + if(pending&&++ticks>=5){ticks=0;pending=false;ClientPlayNetworking.send(new ClayWorkshopMenu.Select(menu.containerId,menu.view.token(),source));} + var clay=menu.getSlot(0).getItem(); + if(previewSource!=source||!ItemStack.matches(lastClay,clay)){ + previewSource=source;lastClay=clay.copy();var colored=ClayWorkshopMenu.material(source,clay); + preview=source==null?ItemStack.EMPTY:StatuaryEntity.item(colored==null?source:colored); + } + } + @Override public void onFilesDrop(List files){if(!files.isEmpty())load(files.getFirst());} + @Override public void removed(){generation++;super.removed();} + @Override public void extractBackground(GuiGraphicsExtractor g,int mx,int my,float partial){ + g.fill(leftPos,topPos,leftPos+imageWidth,topPos+142,0xFFE2DDD3);g.fill(leftPos+2,topPos+2,leftPos+imageWidth-2,topPos+140,0xFFC6C6C6); + g.fill(leftPos+147,topPos+24,leftPos+imageWidth-10,topPos+87,0xFF92958E); + for(var slot:menu.slots)if(slot.x> -1000)g.blitSprite(RenderPipelines.GUI_TEXTURED,Identifier.withDefaultNamespace("container/slot"),leftPos+slot.x-1,topPos+slot.y-1,18,18); + if(!preview.isEmpty()){ + g.pose().pushMatrix();g.pose().translate(leftPos+195,topPos+28);g.pose().scale(3.5f,3.5f);g.item(preview,0,0);g.pose().popMatrix(); + } + g.text(font,Component.literal("→"),leftPos+254,topPos+97,0xFF404040,false); + var detail=font.split(status,128);for(int i=0;i lines,Component text){ + var m=net.minecraft.client.Minecraft.getInstance(); + // Creative search (and item viewers) build tooltips on workers. Font measurement + // can upload a new glyph to the GPU, so even measuring text must stay on the client thread. + // Never enqueue and wait here: the client may itself be waiting for the search index. + if(!m.isSameThread()){ + lines.add(text.copy().withStyle(ChatFormatting.GRAY)); + return; + } + int width=Math.max(100,Math.min(280,m.getWindow().getGuiScaledWidth()-32)); + for(var part:m.font.getSplitter().splitLines(text,width,net.minecraft.network.chat.Style.EMPTY)) + lines.add(Component.literal(part.getString()).withStyle(ChatFormatting.GRAY)); + } } diff --git a/mods/sanctuary/src/main/java/fr/koka/sanctuary/client/FourneauFire.java b/mods/sanctuary/src/main/java/fr/koka/sanctuary/client/FourneauFire.java new file mode 100644 index 0000000..8c525d5 --- /dev/null +++ b/mods/sanctuary/src/main/java/fr/koka/sanctuary/client/FourneauFire.java @@ -0,0 +1,45 @@ +package fr.koka.sanctuary.client; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.particles.ParticleTypes; +import net.minecraft.sounds.SoundEvents; +import net.minecraft.sounds.SoundSource; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.AbstractFurnaceBlock; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.Vec3; + +/** One animation source per facade; texture coordinates are measured on the full 48px face. */ +public final class FourneauFire { + public static Vec3 point(BlockPos origin,Direction facing,double pixelX,double pixelY){ + double u=pixelX/16.0,v=3-pixelY/16.0,edge=0.02; + var local=switch(facing){ + case NORTH -> new Vec3(3-u,v,-edge); + case SOUTH -> new Vec3(u,v,3+edge); + case EAST -> new Vec3(3+edge,v,3-u); + case WEST -> new Vec3(-edge,v,u); + default -> throw new IllegalArgumentException("Horizontal furnace facade required"); + }; + return local.add(origin.getX(),origin.getY(),origin.getZ()); + } + public static boolean animate(BlockState state,Level level,BlockPos pos,RandomSource random){ + var tile=FourneauModels.fireTile(pos); + if(tile==null)return false; + // Cancel all 27 native sources, even when cold; only the front middle cell emits. + if(!state.getValue(AbstractFurnaceBlock.LIT)||!tile.emitter())return true; + var origin=tile.origin(pos); + if(random.nextDouble()<0.1){ + var sound=point(origin,tile.facing(),24,24); + level.playLocalSound(sound.x,sound.y,sound.z,SoundEvents.FURNACE_FIRE_CRACKLE,SoundSource.BLOCKS,1,1,false); + } + for(int row:new int[]{16,37}){ + var p=point(origin,tile.facing(),12+random.nextDouble()*24,row+0.25+random.nextDouble()*0.5); + level.addParticle(ParticleTypes.SMOKE,p.x,p.y,p.z,0,0,0); + level.addParticle(ParticleTypes.FLAME,p.x,p.y,p.z,0,0,0); + } + return true; + } + private FourneauFire(){} +} diff --git a/mods/sanctuary/src/main/java/fr/koka/sanctuary/client/FourneauModels.java b/mods/sanctuary/src/main/java/fr/koka/sanctuary/client/FourneauModels.java index 3416d70..f742739 100644 --- a/mods/sanctuary/src/main/java/fr/koka/sanctuary/client/FourneauModels.java +++ b/mods/sanctuary/src/main/java/fr/koka/sanctuary/client/FourneauModels.java @@ -25,7 +25,15 @@ import net.minecraft.world.level.block.state.BlockState; public final class FourneauModels { private static volatile Map> chunks=Map.of(); private static final List MODELS=new ArrayList<>(); - static {for(int y=0;y<3;y++)for(int z=0;z<3;z++)for(int x=0;x<3;x++)MODELS.add(caskId(x,y,z));for(var d:Direction.Plane.HORIZONTAL)for(int y=0;y<3;y++)for(int z=0;z<3;z++)for(int x=0;x<3;x++)MODELS.add(id(d,x,y,z));} + private static final Map FIRE_TILES=new HashMap<>(); + public record FireTile(Direction facing,int x,int y,int z) { + public BlockPos origin(BlockPos pos){return pos.offset(-x,-y,-z);} + public boolean emitter(){return y==1 && switch(facing){case NORTH -> x==1&&z==0;case SOUTH -> x==1&&z==2;case EAST -> x==2&&z==1;case WEST -> x==0&&z==1;default -> false;};} + } + public static FireTile fireTile(BlockPos pos){return FIRE_TILES.get(modelAt(pos));} + private static Identifier litId(Identifier id){return id.withPath(id.getPath()+"_lit");} + public static Identifier modelAt(BlockPos pos,BlockState state){var id=modelAt(pos);return id!=null&&state.is(Blocks.FURNACE)&&state.getValue(net.minecraft.world.level.block.AbstractFurnaceBlock.LIT)&&FIRE_TILES.containsKey(id)?litId(id):id;} + static {for(int y=0;y<3;y++)for(int z=0;z<3;z++)for(int x=0;x<3;x++)MODELS.add(caskId(x,y,z));for(var d:Direction.Plane.HORIZONTAL)for(int y=0;y<3;y++)for(int z=0;z<3;z++)for(int x=0;x<3;x++){var id=id(d,x,y,z);MODELS.add(id);MODELS.add(litId(id));FIRE_TILES.put(id,new FireTile(d,x,y,z));}} private static Identifier id(Direction d,int x,int y,int z){return SanctuaryMod.id("block/fourneau/"+d.getName()+"_"+x+"_"+y+"_"+z);} private static Identifier caskId(int x,int y,int z){return SanctuaryMod.id("block/fut/"+x+"_"+y+"_"+z);} public static Identifier modelAt(BlockPos pos){var cells=chunks.get(ChunkPos.pack(pos));return cells==null?null:cells.get(pos);} @@ -65,15 +73,15 @@ public final class FourneauModels { private static final class FacadeModel extends WrapperBlockStateModel { private final Map tiled; FacadeModel(BlockStateModel original,Map tiled){super(original);this.tiled=tiled;} - private BlockStateModel select(BlockPos pos){var id=modelAt(pos);return id==null?wrapped:tiled.getOrDefault(id,wrapped);} + private BlockStateModel select(BlockPos pos,BlockState state){var id=modelAt(pos,state);return id==null?wrapped:tiled.getOrDefault(id,wrapped);} @Override public void emitQuads(QuadEmitter emitter,BlockAndTintGetter view,BlockPos pos,BlockState state,RandomSource random,Predicate cull){ - select(pos).emitQuads(emitter,view,pos,state,random,cull); + select(pos,state).emitQuads(emitter,view,pos,state,random,cull); } @Override public Object createGeometryKey(BlockAndTintGetter view,BlockPos pos,BlockState state,RandomSource random){ - var model=select(pos);return model==wrapped?wrapped.createGeometryKey(view,pos,state,random):model; + var model=select(pos,state);return model==wrapped?wrapped.createGeometryKey(view,pos,state,random):model; } - @Override public Material.Baked particleMaterial(BlockAndTintGetter view,BlockPos pos,BlockState state){return select(pos).particleMaterial(view,pos,state);} - @Override public int materialFlags(BlockAndTintGetter view,BlockPos pos,BlockState state,RandomSource random){return select(pos).materialFlags(view,pos,state,random);} + @Override public Material.Baked particleMaterial(BlockAndTintGetter view,BlockPos pos,BlockState state){return select(pos,state).particleMaterial(view,pos,state);} + @Override public int materialFlags(BlockAndTintGetter view,BlockPos pos,BlockState state,RandomSource random){return select(pos,state).materialFlags(view,pos,state,random);} } private FourneauModels(){} } diff --git a/mods/sanctuary/src/main/java/fr/koka/sanctuary/client/InhabitantCompanionWidget.java b/mods/sanctuary/src/main/java/fr/koka/sanctuary/client/InhabitantCompanionWidget.java index 09d183b..f0c250d 100644 --- a/mods/sanctuary/src/main/java/fr/koka/sanctuary/client/InhabitantCompanionWidget.java +++ b/mods/sanctuary/src/main/java/fr/koka/sanctuary/client/InhabitantCompanionWidget.java @@ -14,12 +14,15 @@ import net.minecraft.world.item.SpawnEggItem; public final class InhabitantCompanionWidget extends AbstractWidget { private final Mob model; public InhabitantCompanionWidget(int width,int height,ItemStack egg) { + this(width,height,egg,true); + } + public InhabitantCompanionWidget(int width,int height,ItemStack egg,boolean baby) { super(0,0,width,height,egg.getHoverName()); var client=net.minecraft.client.Minecraft.getInstance(); var entity=client.level!=null && egg.getItem() instanceof SpawnEggItem ? SpawnEggItem.getType(egg).create(client.level,EntitySpawnReason.MOB_SUMMONED) : null; model=entity instanceof Mob mob ? mob : null; - if(model!=null){model.setId(Integer.MIN_VALUE);model.setBaby(true);model.setPos(client.player.position());model.setCustomName(egg.getCustomName());} + if(model!=null){model.setId(Integer.MIN_VALUE);model.setBaby(baby);model.setPos(client.player.position());model.setCustomName(egg.getCustomName());} } @Override protected void extractWidgetRenderState(GuiGraphicsExtractor g,int mouseX,int mouseY,float partial) { if(model==null)return; diff --git a/mods/sanctuary/src/main/java/fr/koka/sanctuary/client/InlineAdvancements.java b/mods/sanctuary/src/main/java/fr/koka/sanctuary/client/InlineAdvancements.java new file mode 100644 index 0000000..b4e39dc --- /dev/null +++ b/mods/sanctuary/src/main/java/fr/koka/sanctuary/client/InlineAdvancements.java @@ -0,0 +1,72 @@ +package fr.koka.sanctuary.client; + +import java.util.*; +import fr.koka.sanctuary.mixin.client.NotificationAdvancementTabAccess; +import net.minecraft.advancements.*; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphicsExtractor; +import net.minecraft.client.gui.components.AbstractWidget; +import net.minecraft.client.gui.narration.NarrationElementOutput; +import net.minecraft.client.gui.screens.advancements.*; +import net.minecraft.client.input.*; +import net.minecraft.network.chat.Component; + +/** The actual native advancement tree, embedded below skills without a second screen. */ +final class InlineAdvancements extends AbstractWidget { + private final List tabs=new ArrayList<>(); + private int selected,nodeCount; + private AdvancementHolder pressed; + private double dragged; + InlineAdvancements(int width){ + super(0,0,width,155,Component.translatable("gui.advancements")); + rebuildTree(); + } + void copyPosition(InlineAdvancements previous){if(previous==null)return;selected=previous.selected;for(var t:tabs)for(var old:previous.tabs)if(t.getRootAdvancement().equals(old.getRootAdvancement()))t.copyPosition(old);} + private void rebuildTree(){ + var old=new ArrayList<>(tabs);tabs.clear(); + var mc=Minecraft.getInstance();var manager=mc.player.connection.getAdvancements();nodeCount=manager.tree().nodes().size(); + var owner=new AdvancementsScreen(manager);owner.width=mc.getWindow().getGuiScaledWidth(); + for(var root:manager.tree().roots()){ + var tab=AdvancementTab.create(mc,owner,0,root);if(tab==null)continue; + for(var node:manager.tree().tasks())if(node.root().equals(root))tab.addAdvancement(node); + for(var prior:old)if(prior.getRootAdvancement().equals(tab.getRootAdvancement()))tab.copyPosition(prior); + tabs.add(tab); + } + } + private float scale(){return Math.min(1,(width-4)/234f);} + private int treeX(){return getX()+(width-Math.round(234*scale()))/2;} + private int treeY(){return getY()+27;} + private AdvancementTab tab(){return tabs.isEmpty()?null:tabs.get(Math.floorMod(selected,tabs.size()));} + private AdvancementHolder hit(double x,double y){ + var t=tab();if(t==null)return null;int rx=(int)((x-treeX())/scale()),ry=(int)((y-treeY())/scale()); + if(rx<0||rx>=234||ry<0||ry>=113)return null; + var a=(NotificationAdvancementTabAccess)t; + for(var w:a.sanctuary$widgets().values())if(w.isMouseOver((int)Math.floor(a.sanctuary$scrollX()),(int)Math.floor(a.sanctuary$scrollY()),rx,ry))return w.getAdvancement(); + return null; + } + @Override protected void extractWidgetRenderState(GuiGraphicsExtractor g,int mx,int my,float dt){ + var mc=Minecraft.getInstance();if(nodeCount!=mc.player.connection.getAdvancements().tree().nodes().size())rebuildTree();g.text(mc.font,getMessage(),getX(),getY(),0xFFA9D7A0);var t=tab(); + if(t==null){g.text(mc.font,Component.translatable("advancements.empty"),getX(),getY()+30,0xFFCCCCCC);return;} + g.text(mc.font,"‹",getX()+2,getY()+14,0xFFFFFF55);g.text(mc.font,"›",getRight()-8,getY()+14,0xFFFFFF55); + g.centeredText(mc.font,t.getTitle(),getX()+width/2,getY()+14,0xFFFFFFFF); + var manager=mc.player.connection.getAdvancements();for(var entry:manager.progress().entrySet()){var w=t.getWidget(entry.getKey());if(w!=null)w.setProgress(entry.getValue());} + g.pose().pushMatrix();g.pose().translate(treeX(),treeY());g.pose().scale(scale(),scale());t.extractContents(g,0,0);g.pose().popMatrix(); + var holder=hit(mx,my); + if(holder!=null&&isHovered()){ + var display=holder.value().display().orElseThrow();var progress=manager.progress().get(holder); + var lines=new ArrayList();lines.add(display.title());lines.add(display.description()); + if(progress!=null&&progress.getProgressText()!=null)lines.add(progress.getProgressText()); + if(NotificationClient.canTrack(holder))lines.add(NotificationClient.label(NotificationClient.isPinned(holder.id())?"unpin_hint":"pin_hint")); + g.setTooltipForNextFrame(mc.font,lines,Optional.empty(),mx,my); + } + } + @Override protected boolean isValidClickButton(MouseButtonInfo b){return b.button()==0||b.button()==1;} + @Override public void onClick(MouseButtonEvent e,boolean twice){ + if(e.y()=getY()+10){selected+=e.x() statues=java.util.Set.of(); + public static boolean knows(net.minecraft.world.entity.EntityType type){return available()&&statues.contains(net.minecraft.core.registries.BuiltInRegistries.ENTITY_TYPE.getKey(type).toString());} + public static void register(){ + ClientPlayNetworking.registerGlobalReceiver(Metatable.Open.TYPE,(p,c)->c.client().execute(()->{ + leave();origin=p.origin();level=c.client().level;statues=java.util.Set.copyOf(p.statues()); + if(available())c.client().gui.setScreen(PlansScreen.workshop());else leave(); + })); + ClientPlayConnectionEvents.DISCONNECT.register((h,c)->leave()); + ClientConfigurationConnectionEvents.INIT.register((h,c)->leave()); + } + public static boolean available(){ + var m=Minecraft.getInstance(); + return origin!=null&&m.level==level&&m.player!=null&&m.player.isAlive()&&!m.player.isSpectator() + &&Metatable.members(origin).stream().anyMatch(p->m.player.getEyePosition().distanceToSqr(Vec3.atCenterOf(p))<64) + &&Metatable.state(level,origin,true)==1; + } + public static int session(){return session;} + public static boolean available(int expected){return expected==session&&available();} + public static void leave(){session++;origin=null;level=null;statues=java.util.Set.of();} + public static void selected(){leave();Minecraft.getInstance().gui.setScreen(null);} + public static void needWorkshop(){var m=Minecraft.getInstance();if(m.player!=null)m.gui.hud.setOverlayMessage(PlanClient.label("workshop_required"),false);} + private MetatableClient(){} +} diff --git a/mods/sanctuary/src/main/java/fr/koka/sanctuary/client/ModelImportErrors.java b/mods/sanctuary/src/main/java/fr/koka/sanctuary/client/ModelImportErrors.java new file mode 100644 index 0000000..d759235 --- /dev/null +++ b/mods/sanctuary/src/main/java/fr/koka/sanctuary/client/ModelImportErrors.java @@ -0,0 +1,13 @@ +package fr.koka.sanctuary.client; + +import net.minecraft.network.chat.Component; + +final class ModelImportErrors { + static Component message(Throwable error){ + String key=error==null?"":String.valueOf(error.getMessage()); + if(key.startsWith("glb_"))return Component.translatable("sanctuary.statuary.error."+key.substring(4)); + if(key.equals("model_limit")||key.equals("block_limit")||key.equals("size_limit"))return Component.translatable("sanctuary.statuary.error.model_limit"); + return Component.translatable("sanctuary.statuary.error.file"); + } + private ModelImportErrors(){} +} diff --git a/mods/sanctuary/src/main/java/fr/koka/sanctuary/client/MultiblockScreen.java b/mods/sanctuary/src/main/java/fr/koka/sanctuary/client/MultiblockScreen.java index d862952..f34b9d1 100644 --- a/mods/sanctuary/src/main/java/fr/koka/sanctuary/client/MultiblockScreen.java +++ b/mods/sanctuary/src/main/java/fr/koka/sanctuary/client/MultiblockScreen.java @@ -10,6 +10,7 @@ import net.minecraft.world.entity.player.Inventory; public final class MultiblockScreen extends ContainerScreen { private EditBox search; private Button previous,next,apply; + private double wheel; private final java.util.List