Sanctuary 26.2.0-alpha.198 — automatisation du tonneau de fermentation #4

Merged
koka merged 3 commits from release/26.2.0-alpha.198 into main 2026-08-27 05:44:48 +00:00
19 changed files with 360 additions and 55 deletions
+12
View File
@@ -6,6 +6,18 @@ Ce journal suit les **versions du modpack**. Lorsqu'un seul module change, sa ve
- Depuis `26.2.0-alpha.6`, le pack et les modules ont des versions indépendantes. I Like To Move It et Only Fun deviennent les cinquième et sixième modules actifs dans l'alpha 46.
- Les versions ci-dessous ont été reconstituées à partir des JAR archivés, des packs locaux et du code source actuel. Les dates n'étant pas enregistrées de manière fiable, elles ne sont pas inventées.
## `26.2.0-alpha.198`
Modules modifiés : **It's Alive `0.0.0-alpha.30`** et **Sanctuary `0.0.0-alpha.114`**. Les huit autres modules restent inchangés.
- limite chaque emplacement d'entrée du tonneau de fermentation à une seule unité ;
- refuse les doublons et les ingrédients qui ne peuvent plus compléter aucune recette de fermentation chargée ;
- bloque ainsi les hoppers dès qu'une recette complète est assemblée, au lieu d'absorber progressivement tout leur inventaire ;
- expose dans le `BlockState` du tonneau les transitions vide, un à quatre ingrédients et résultat prêt afin que les observateurs vanilla émettent une impulsion fiable ;
- conserve le comparateur pour lire le remplissage et l'état prêt sans générer d'impulsion à chaque tick de fermentation ;
- aligne le shift-clic de linventaire étendu sur vanilla : la hotbar alimente toutes les rangées de stockage débloquées, rangées 5 et 6 comprises, tandis que le stockage et loverflow nalimentent que la hotbar ;
- conserve les piles déjà présentes dans les anciennes sauvegardes, tous les IDs, les données It's Alive v1 et le protocole 3, sans migration destructive.
## `26.2.0-alpha.197`
Modules modifiés : **It's Alive `0.0.0-alpha.29`** et **Sanctuary `0.0.0-alpha.113`**. Les huit autres modules restent inchangés.
+2 -2
View File
@@ -22,9 +22,9 @@ Licence : **GNU GPL 3.0 or Later** (`GPL-3.0-or-later`)
Les descriptions FR/EN/RU sont déclarées dans chaque `fabric.mod.json` et dans les fichiers de langue. Les images fournies servent d'icônes Fabric de leur module.
Version courante du pack local : **`26.2.0-alpha.197`**. Sanctuary passe en **`0.0.0-alpha.114`** ; It's Alive reste à **`0.0.0-alpha.29`** et les huit autres modules restent inchangés.
Version courante du pack local : **`26.2.0-alpha.198`**. It's Alive passe en **`0.0.0-alpha.30`** et Sanctuary en **`0.0.0-alpha.114`** ; les huit autres modules restent inchangés.
Le shift-clic de linventaire suit le comportement vanilla sur toutes les rangées débloquées : la hotbar alimente le stockage, rangées 5 et 6 comprises, et une rangée de stockage ou doverflow nalimente que la hotbar.
Lalpha.198 borne lautomatisation du tonneau de fermentation : chaque emplacement naccepte quune unité utile à une recette encore réalisable, les doublons invalides sont refusés et les observateurs suivent les changements visibles du contenu sans impulsion de progression parasite. Elle intègre aussi la correction de Chris : le shift-clic de linventaire suit le comportement vanilla sur toutes les rangées débloquées, y compris les rangées 5 et 6, tandis quune rangée de stockage ou doverflow nalimente que la hotbar.
Lalpha.197 ajoute un panneau communautaire de demandes de quêtes dans Sanctuary. Le demandeur renseigne son pseudonyme, un objectif, un type KILL/BRING/PAY, une quantité et une cible validée par le serveur ; un autre joueur prend la demande au clic droit et peut labandonner accroupi. Le panneau reste récupérable uniquement par son propriétaire ou un opérateur. La marmite Its Alive regroupe désormais jusqu’à 64 objets strictement identiques dans une même pile logique et cuit un lot borné par lingrédient le plus rare et la capacité de sortie, afin de produire directement une pile de sel, de crème ou dautres préparations.
+68 -18
View File
@@ -3427,7 +3427,7 @@ tasks.register("verifyProgressivePauseMenuRelease") {
[(locale): new JsonSlurper().parse(file(
"sanctuary/src/main/resources/assets/sanctuary/lang/${locale}.json"))]
}
if (rootProject.pack_version != "26.2.0-alpha.197"
if (rootProject.pack_version != "26.2.0-alpha.198"
|| rootProject.sanctuary_version != "0.0.0-alpha.114"
|| rootProject.onlyfun_version != "0.0.0-alpha.19"
|| release.pack_version != rootProject.pack_version || index.versionId != rootProject.pack_version
@@ -3477,7 +3477,7 @@ tasks.register("verifyMirrorLavenderDrawerRelease") {
def redStonerRegistry = file(
"redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/registry/RedStonerRegistries.java").text
def drawerRecipe = file("redstoner/src/main/resources/data/redstoner/recipe/drawer.json")
if (rootProject.pack_version != "26.2.0-alpha.197"
if (rootProject.pack_version != "26.2.0-alpha.198"
|| rootProject.anotherworld_version != "0.0.0-alpha.49"
|| rootProject.redstoner_version != "0.0.0-alpha.10"
|| release.pack_version != rootProject.pack_version || index.versionId != rootProject.pack_version
@@ -4121,7 +4121,7 @@ tasks.register("verifyShopPricingLayoutRelease") {
"pack/migrations/26.2.0-alpha.192-shop-pricing-layout-to-alpha.193.json"))
def release = new JsonSlurper().parse(file("pack/release.json"))
def index = new JsonSlurper().parse(file("pack/prism/modrinth.index.json"))
if (rootProject.pack_version != "26.2.0-alpha.197"
if (rootProject.pack_version != "26.2.0-alpha.198"
|| rootProject.onlyfun_version != "0.0.0-alpha.19"
|| rootProject.sanctuary_version != "0.0.0-alpha.114"
|| release.pack_version != rootProject.pack_version || index.versionId != rootProject.pack_version
@@ -4166,8 +4166,8 @@ tasks.register("verifyCuisineCropsRelease") {
def climate = file("itsalive/src/main/java/fr/koka99cab/sanctuary26/itsalive/agriculture/ClimateCropForaging.java").text
def rice = file("itsalive/src/main/java/fr/koka99cab/sanctuary26/itsalive/agriculture/RiceCropBlock.java").text
def bean = file("itsalive/src/main/java/fr/koka99cab/sanctuary26/itsalive/agriculture/BeanCropBlock.java").text
if (rootProject.pack_version != "26.2.0-alpha.197"
|| rootProject.itsalive_version != "0.0.0-alpha.29"
if (rootProject.pack_version != "26.2.0-alpha.198"
|| rootProject.itsalive_version != "0.0.0-alpha.30"
|| release.pack_version != rootProject.pack_version || index.versionId != rootProject.pack_version
|| migration.source?.pack_version != "26.2.0-alpha.193"
|| migration.source?.modules != [itsalive: "0.0.0-alpha.26"]
@@ -4215,7 +4215,7 @@ tasks.register("verifyShopCsvBossGatesRelease") {
def bossGate = file("onlyfun/src/main/java/fr/koka99cab/sanctuary26/onlyfun/shop/OnlyFunBossShopGate.java").text
def catalog = file("onlyfun/src/main/java/fr/koka99cab/sanctuary26/onlyfun/shop/OnlyFunShopCatalog.java").text
def offerCatalog = file("sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/ShopOfferCatalog.java").text
if (rootProject.pack_version != "26.2.0-alpha.197"
if (rootProject.pack_version != "26.2.0-alpha.198"
|| rootProject.onlyfun_version != "0.0.0-alpha.19"
|| rootProject.sanctuary_version != "0.0.0-alpha.114"
|| release.pack_version != rootProject.pack_version || index.versionId != rootProject.pack_version
@@ -4261,7 +4261,7 @@ tasks.register("verifyCulinaryMailboxGrappleFixesRelease") {
def release = new JsonSlurper().parse(file("pack/release.json"))
def prism = new JsonSlurper().parse(file("pack/prism/modrinth.index.json"))
def packwiz = file("pack/packwiz/pack.toml").text
if (rootProject.pack_version != "26.2.0-alpha.197"
if (rootProject.pack_version != "26.2.0-alpha.198"
|| migration.source?.pack_version != "26.2.0-alpha.195"
|| migration.target?.pack_version != "26.2.0-alpha.196"
|| migration.target?.modules != [
@@ -4279,10 +4279,10 @@ tasks.register("verifyCulinaryMailboxGrappleFixesRelease") {
|| migration.distribution?.jei?.target != "30.26.0.182"
|| migration.compatibility?.save_migration_required != false
|| migration.compatibility?.registry_ids_changed != false
|| release.pack_version != "26.2.0-alpha.197"
|| release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.197.mrpack"
|| prism.versionId != "26.2.0-alpha.197"
|| !packwiz.contains('version = "26.2.0-alpha.197"')) {
|| release.pack_version != "26.2.0-alpha.198"
|| release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.198.mrpack"
|| prism.versionId != "26.2.0-alpha.198"
|| !packwiz.contains('version = "26.2.0-alpha.198"')) {
throw new GradleException("The alpha.196 culinary, mailbox and grapple release contract is incomplete")
}
}
@@ -4307,9 +4307,9 @@ tasks.register("verifyQuestRequestsCauldronBatchesRelease") {
def prism = new JsonSlurper().parse(file("pack/prism/modrinth.index.json"))
def packwiz = file("pack/packwiz/pack.toml").text
def sanctuaryManifest = new JsonSlurper().parse(file("sanctuary/src/main/resources/fabric.mod.json"))
if (rootProject.pack_version != "26.2.0-alpha.197"
|| rootProject.itsalive_version != "0.0.0-alpha.29"
|| rootProject.sanctuary_version != "0.0.0-alpha.113"
if (rootProject.pack_version != "26.2.0-alpha.198"
|| rootProject.itsalive_version != "0.0.0-alpha.30"
|| rootProject.sanctuary_version != "0.0.0-alpha.114"
|| migration.source?.pack_version != "26.2.0-alpha.196"
|| migration.target?.pack_version != "26.2.0-alpha.197"
|| migration.target?.modules != [itsalive: "0.0.0-alpha.29", sanctuary: "0.0.0-alpha.113"]
@@ -4327,10 +4327,10 @@ tasks.register("verifyQuestRequestsCauldronBatchesRelease") {
|| migration.distribution?.jei?.target != "30.26.0.186"
|| migration.compatibility?.sanctuary_target_network_protocol != 19
|| sanctuaryManifest.custom?.sanctuary26?.network_protocol != 19
|| release.pack_version != "26.2.0-alpha.197"
|| release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.197.mrpack"
|| prism.versionId != "26.2.0-alpha.197"
|| !packwiz.contains('version = "26.2.0-alpha.197"')) {
|| release.pack_version != "26.2.0-alpha.198"
|| release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.198.mrpack"
|| prism.versionId != "26.2.0-alpha.198"
|| !packwiz.contains('version = "26.2.0-alpha.198"')) {
throw new GradleException("The alpha.197 quest-request and cauldron-batch release contract is incomplete")
}
}
@@ -4340,6 +4340,56 @@ tasks.named("verifyWorkspace") {
dependsOn tasks.named("verifyQuestRequestsCauldronBatchesRelease")
}
tasks.register("verifyFermentationBarrelAutomationRelease") {
group = "verification"
description = "Checks the complete Sanctuary 26.2.0-alpha.198 fermentation barrel release."
inputs.file(file("pack/migrations/26.2.0-alpha.197-fermentation-barrel-automation-to-alpha.198.json"))
inputs.file(file("pack/release.json"))
inputs.file(file("pack/prism/modrinth.index.json"))
inputs.file(file("pack/packwiz/pack.toml"))
inputs.file(file("pack/packwiz/index.toml"))
inputs.file(file("itsalive/src/main/resources/fabric.mod.json"))
doLast {
def migration = new JsonSlurper().parse(file(
"pack/migrations/26.2.0-alpha.197-fermentation-barrel-automation-to-alpha.198.json"))
def release = new JsonSlurper().parse(file("pack/release.json"))
def prism = new JsonSlurper().parse(file("pack/prism/modrinth.index.json"))
def packwiz = file("pack/packwiz/pack.toml").text
def packwizIndex = file("pack/packwiz/index.toml").text
def itsAliveManifest = new JsonSlurper().parse(file("itsalive/src/main/resources/fabric.mod.json"))
if (rootProject.pack_version != "26.2.0-alpha.198"
|| rootProject.itsalive_version != "0.0.0-alpha.30"
|| migration.source?.pack_version != "26.2.0-alpha.197"
|| migration.target?.pack_version != "26.2.0-alpha.198"
|| migration.target?.modules != [itsalive: "0.0.0-alpha.30", sanctuary: "0.0.0-alpha.114"]
|| migration.fermentation_barrel?.maximum_units_per_input_slot != 1
|| migration.fermentation_barrel?.loaded_recipe_prefix_filter != true
|| migration.fermentation_barrel?.observer_state?.vanilla_neighbor_updates != true
|| migration.inventory_shift_click?.owner != "sanctuary"
|| migration.inventory_shift_click?.hotbar_to_all_unlocked_storage_rows != true
|| migration.inventory_shift_click?.storage_and_overflow_to_hotbar_only != true
|| migration.inventory_shift_click?.shift_double_click_respects_inventory_groups != true
|| migration.compatibility?.itsalive_data_version != 1
|| migration.compatibility?.itsalive_network_protocol != 3
|| migration.compatibility?.save_migration_required != false
|| migration.compatibility?.registry_ids_changed != false
|| migration.distribution?.packwiz_index_refreshed != true
|| itsAliveManifest.custom?.sanctuary26?.data_version != 1
|| itsAliveManifest.custom?.sanctuary26?.network_protocol != 3
|| release.pack_version != "26.2.0-alpha.198"
|| release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.198.mrpack"
|| prism.versionId != "26.2.0-alpha.198"
|| !packwiz.contains('version = "26.2.0-alpha.198"')
|| !packwizIndex.contains('file = "mods/itsalive-0.0.0-alpha.30.jar"')) {
throw new GradleException("The alpha.198 fermentation barrel release contract is incomplete")
}
}
}
tasks.named("verifyWorkspace") {
dependsOn tasks.named("verifyFermentationBarrelAutomationRelease")
}
tasks.named("check") {
dependsOn tasks.named("verifyWorkspace")
dependsOn tasks.named("verifyTransport")
+2 -2
View File
@@ -18,7 +18,7 @@ redstoner_version=0.0.0-alpha.10
redstoner_lifecycle=active
anotherworld_version=0.0.0-alpha.49
anotherworld_lifecycle=active
itsalive_version=0.0.0-alpha.29
itsalive_version=0.0.0-alpha.30
itsalive_lifecycle=active
ouch_version=0.0.0-alpha.8
ouch_lifecycle=active
@@ -30,5 +30,5 @@ onlyfun_version=0.0.0-alpha.19
onlyfun_lifecycle=active
sanctuary_version=0.0.0-alpha.114
sanctuary_lifecycle=active
pack_version=26.2.0-alpha.197
pack_version=26.2.0-alpha.198
maven_group=fr.koka99cab.sanctuary26
+3
View File
@@ -220,6 +220,7 @@ Planche Dalle bois Planche
- L'ingrédient ou le résultat est visible sur le couvercle et l'overlay indique le temps restant.
- Un clic droit récupère le produit fini ; sinon il retire le dernier ingrédient.
- Hopper au-dessus ou sur les côtés : ingrédients ; hopper dessous : résultat.
- Chaque emplacement d'entrée accepte une seule unité. L'automatisation refuse les doublons et tout ajout qui ne peut plus compléter une recette connue, puis se verrouille naturellement dès que la recette est complète.
### Redstone et comparateurs
@@ -227,6 +228,8 @@ Les six postes — presse, moulin, poêle, marmite, four et tonneau — fourniss
Les observateurs suivent le comportement vanilla : ils émettent une impulsion quand le bloc change réellement d'état visible, par exemple lorsque la presse change de niveau, que la marmite change de mélange ou qu'un appareil s'allume ou s'éteint. Ils ne réagissent pas à chaque tick de progression ni aux changements d'inventaire invisibles ; pour ces informations, il faut utiliser un comparateur.
Le tonneau de fermentation expose également ses transitions de contenu : vide, un à quatre ingrédients acceptés, puis résultat prêt. Un observateur placé contre lui réagit à chacune de ces transitions, mais pas à chaque tick de macération.
## Ingrédients de base connus
Ces transformations sont disponibles sans page secrète.
+55 -3
View File
@@ -11,6 +11,14 @@ tasks.register("culinaryBatchingSmoke", JavaExec) {
mainClass = "fr.koka99cab.sanctuary26.itsalive.culinary.recipe.CulinaryBatchingSmoke"
}
tasks.register("culinaryPartialMatchingSmoke", JavaExec) {
group = "verification"
description = "Checks shapeless recipe prefixes used by hopper-fed fermentation barrels."
dependsOn tasks.named("testClasses")
classpath = sourceSets.test.runtimeClasspath
mainClass = "fr.koka99cab.sanctuary26.itsalive.culinary.recipe.CulinaryPartialMatchingSmoke"
}
tasks.named("test") {
// Quantity contracts are executable smoke tasks, not JUnit engine tests.
failOnNoDiscoveredTests = false
@@ -409,7 +417,7 @@ tasks.register("verifyItsAlive") {
def climateSource = file("src/main/java/fr/koka99cab/sanctuary26/itsalive/agriculture/ClimateCropForaging.java").text
def pineappleSource = file("src/main/java/fr/koka99cab/sanctuary26/itsalive/agriculture/PineappleItem.java").text
def worldgenSource = file("src/main/java/fr/koka99cab/sanctuary26/itsalive/agriculture/ItsAliveAgricultureWorldGeneration.java").text
if (project.version.toString() != "0.0.0-alpha.29"
if (project.version.toString() != "0.0.0-alpha.30"
|| climateMigration.target?.pack_version != "26.2.0-alpha.157"
|| climateMigration.target?.modules?.itsalive != "0.0.0-alpha.24"
|| !climateSource.contains("PlayerBlockBreakEvents.AFTER.register")
@@ -1141,7 +1149,7 @@ tasks.register("verifyCulinaryReliability") {
}
}
}
if (project.version.toString() != "0.0.0-alpha.29"
if (project.version.toString() != "0.0.0-alpha.30"
|| migration.target?.modules?.itsalive != "0.0.0-alpha.28"
|| migration.culinary_fixes?.food_press_contents_destroyed_on_break != true
|| migration.culinary_fixes?.comparator_contract?.blocks?.size() != 6
@@ -1179,7 +1187,7 @@ tasks.register("verifyCauldronBatching") {
def recipe = file("src/main/java/fr/koka99cab/sanctuary26/itsalive/culinary/recipe/CulinaryProcessRecipe.java").text
def cauldron = file("src/main/java/fr/koka99cab/sanctuary26/itsalive/culinary/block/entity/CookingCauldronBlockEntity.java").text
def timed = file("src/main/java/fr/koka99cab/sanctuary26/itsalive/culinary/block/entity/AbstractTimedCulinaryBlockEntity.java").text
if (project.version.toString() != "0.0.0-alpha.29"
if (project.version.toString() != "0.0.0-alpha.30"
|| migration.target?.modules?.itsalive != "0.0.0-alpha.29"
|| migration.cauldron_batches?.logical_stack_limit != 64
|| migration.cauldron_batches?.identity != "same_item_same_components"
@@ -1198,9 +1206,53 @@ tasks.register("verifyCauldronBatching") {
}
}
tasks.register("verifyFermentationBarrelAutomation") {
group = "verification"
description = "Checks alpha.30 bounded hopper input and vanilla observer states for fermentation barrels."
inputs.file(rootProject.file(
"pack/migrations/26.2.0-alpha.197-fermentation-barrel-automation-to-alpha.198.json"))
inputs.file(file("src/main/java/fr/koka99cab/sanctuary26/itsalive/culinary/block/FermentationBarrelBlock.java"))
inputs.file(file("src/main/java/fr/koka99cab/sanctuary26/itsalive/culinary/block/entity/FermentationBarrelBlockEntity.java"))
inputs.file(file("src/main/java/fr/koka99cab/sanctuary26/itsalive/culinary/recipe/CulinaryProcessRecipe.java"))
inputs.file(file("src/main/java/fr/koka99cab/sanctuary26/itsalive/culinary/recipe/CulinaryPartialMatching.java"))
inputs.file(file("src/main/resources/assets/itsalive/blockstates/fermentation_barrel.json"))
doLast {
def migration = new JsonSlurper().parse(rootProject.file(
"pack/migrations/26.2.0-alpha.197-fermentation-barrel-automation-to-alpha.198.json"))
def block = file("src/main/java/fr/koka99cab/sanctuary26/itsalive/culinary/block/FermentationBarrelBlock.java").text
def barrel = file("src/main/java/fr/koka99cab/sanctuary26/itsalive/culinary/block/entity/FermentationBarrelBlockEntity.java").text
def recipe = file("src/main/java/fr/koka99cab/sanctuary26/itsalive/culinary/recipe/CulinaryProcessRecipe.java").text
def partialMatching = file("src/main/java/fr/koka99cab/sanctuary26/itsalive/culinary/recipe/CulinaryPartialMatching.java").text
def blockstate = new JsonSlurper().parse(
file("src/main/resources/assets/itsalive/blockstates/fermentation_barrel.json"))
if (project.version.toString() != "0.0.0-alpha.30"
|| migration.source?.modules?.itsalive != "0.0.0-alpha.29"
|| migration.target?.modules?.itsalive != "0.0.0-alpha.30"
|| migration.fermentation_barrel?.maximum_units_per_input_slot != 1
|| migration.fermentation_barrel?.loaded_recipe_prefix_filter != true
|| migration.fermentation_barrel?.further_input_rejected_when_recipe_complete != true
|| migration.fermentation_barrel?.observer_state?.property != "contents"
|| migration.fermentation_barrel?.observer_state?.progress_tick_updates != false
|| migration.compatibility?.existing_input_stacks_preserved != true
|| !block.contains('IntegerProperty.create("contents", 0, 5)')
|| !block.contains("builder.add(FACING, CONTENTS)")
|| !barrel.contains("getMaxStackSize(ItemStack stack)")
|| !barrel.contains("!getItem(slot).isEmpty()")
|| !barrel.contains("CulinaryRecipeTypes.acceptsPartial")
|| !barrel.contains("state.setValue(FermentationBarrelBlock.CONTENTS, contents), Block.UPDATE_ALL")
|| !recipe.contains("acceptsPartial(List<ItemStack> stacks)")
|| !partialMatching.contains("canAssignDistinctIngredients")
|| blockstate.multipart?.size() != 6) {
throw new GradleException("It's Alive alpha.30 fermentation barrel automation contract is incomplete")
}
}
}
tasks.named("check") {
dependsOn tasks.named("verifyItsAlive")
dependsOn tasks.named("verifyCulinaryReliability")
dependsOn tasks.named("verifyCauldronBatching")
dependsOn tasks.named("culinaryBatchingSmoke")
dependsOn tasks.named("verifyFermentationBarrelAutomation")
dependsOn tasks.named("culinaryPartialMatchingSmoke")
}
@@ -26,11 +26,14 @@ import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.EnumProperty;
import net.minecraft.world.level.block.state.properties.IntegerProperty;
import net.minecraft.world.phys.BlockHitResult;
/** World-time fermentation inventory with top/side input and bottom hopper output. */
public final class FermentationBarrelBlock extends BaseEntityBlock {
public static final EnumProperty<Direction> FACING = BlockStateProperties.FACING;
/** 0 empty, 1-4 accepted ingredients, 5 finished product. */
public static final IntegerProperty CONTENTS = IntegerProperty.create("contents", 0, 5);
private final Supplier<? extends BlockEntityType<FermentationBarrelBlockEntity>> blockEntityType;
private final MapCodec<FermentationBarrelBlock> instanceCodec;
@@ -41,7 +44,9 @@ public final class FermentationBarrelBlock extends BaseEntityBlock {
super(properties);
this.blockEntityType = blockEntityType;
this.instanceCodec = MapCodec.unit(this);
registerDefaultState(stateDefinition.any().setValue(FACING, Direction.NORTH));
registerDefaultState(stateDefinition.any()
.setValue(FACING, Direction.NORTH)
.setValue(CONTENTS, 0));
}
@Override
@@ -173,6 +178,6 @@ public final class FermentationBarrelBlock extends BaseEntityBlock {
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
builder.add(FACING);
builder.add(FACING, CONTENTS);
}
}
@@ -5,6 +5,8 @@ import fr.koka99cab.sanctuary26.itsalive.culinary.block.FermentationBarrelBlock;
import fr.koka99cab.sanctuary26.itsalive.culinary.recipe.CulinaryProcessRecipe;
import fr.koka99cab.sanctuary26.itsalive.culinary.recipe.CulinaryRecipeInput;
import fr.koka99cab.sanctuary26.itsalive.culinary.recipe.CulinaryRecipeTypes;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
@@ -12,6 +14,7 @@ import net.minecraft.resources.Identifier;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.RecipeHolder;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.storage.ValueInput;
@@ -41,6 +44,7 @@ public final class FermentationBarrelBlockEntity extends AbstractCulinaryContain
ServerLevel level, BlockPos pos, BlockState state, FermentationBarrelBlockEntity barrel
) {
barrel.process(level);
barrel.syncObservableState();
}
private void process(ServerLevel level) {
@@ -84,7 +88,14 @@ public final class FermentationBarrelBlockEntity extends AbstractCulinaryContain
}
public int insertIngredient(ItemStack source, int amount) {
return insert(source, amount, 0, INPUT_SLOTS, this::isFermentationIngredient);
if (source.isEmpty() || amount <= 0) return 0;
for (int slot = 0; slot < INPUT_SLOTS; slot++) {
if (!canPlaceItem(slot, source)) continue;
items.set(slot, source.copyWithCount(1));
contentsChanged(slot);
return 1;
}
return 0;
}
private boolean isFermentationIngredient(ItemStack stack) {
@@ -177,7 +188,20 @@ public final class FermentationBarrelBlockEntity extends AbstractCulinaryContain
@Override
public boolean canPlaceItem(int slot, ItemStack stack) {
return slot >= 0 && slot < INPUT_SLOTS && isFermentationIngredient(stack);
if (slot < 0 || slot >= INPUT_SLOTS || !getItem(slot).isEmpty()
|| hasFinishedProduct() || !isFermentationIngredient(stack)
|| !(level instanceof ServerLevel serverLevel)) {
return false;
}
List<ItemStack> candidate = new ArrayList<>(inputView(0, INPUT_SLOTS));
candidate.set(slot, stack.copyWithCount(1));
return CulinaryRecipeTypes.acceptsPartial(
serverLevel, CulinaryAppliance.FERMENTATION_BARREL, candidate);
}
@Override
public int getMaxStackSize(ItemStack stack) {
return 1;
}
@Override
@@ -204,6 +228,12 @@ public final class FermentationBarrelBlockEntity extends AbstractCulinaryContain
}
}
@Override
protected void sync() {
super.sync();
syncObservableState();
}
private void resetFermentation() {
if (startedAtGameTime >= 0 || activeRecipe != null || activeDuration != 0) {
startedAtGameTime = -1L;
@@ -213,6 +243,25 @@ public final class FermentationBarrelBlockEntity extends AbstractCulinaryContain
}
}
private void syncObservableState() {
if (level == null) return;
int contents = hasFinishedProduct() ? 5 : occupiedInputSlots();
BlockState state = getBlockState();
if (state.hasProperty(FermentationBarrelBlock.CONTENTS)
&& state.getValue(FermentationBarrelBlock.CONTENTS) != contents) {
level.setBlock(worldPosition,
state.setValue(FermentationBarrelBlock.CONTENTS, contents), Block.UPDATE_ALL);
}
}
private int occupiedInputSlots() {
int occupied = 0;
for (int slot = 0; slot < INPUT_SLOTS; slot++) {
if (!getItem(slot).isEmpty()) occupied++;
}
return occupied;
}
@Override
protected void saveAdditional(ValueOutput output) {
super.saveAdditional(output);
@@ -0,0 +1,30 @@
package fr.koka99cab.sanctuary26.itsalive.culinary.recipe;
/** Pure bipartite matching used to validate shapeless recipe prefixes. */
public final class CulinaryPartialMatching {
private CulinaryPartialMatching() {
}
public static boolean canAssignDistinctIngredients(boolean[][] compatibility) {
if (compatibility.length == 0) return true;
int ingredientCount = compatibility[0].length;
if (compatibility.length > ingredientCount) return false;
for (boolean[] row : compatibility) {
if (row.length != ingredientCount) {
throw new IllegalArgumentException("Compatibility matrix must be rectangular");
}
}
return assign(compatibility, 0, new boolean[ingredientCount]);
}
private static boolean assign(boolean[][] compatibility, int stackIndex, boolean[] usedIngredients) {
if (stackIndex >= compatibility.length) return true;
for (int ingredientIndex = 0; ingredientIndex < usedIngredients.length; ingredientIndex++) {
if (usedIngredients[ingredientIndex] || !compatibility[stackIndex][ingredientIndex]) continue;
usedIngredients[ingredientIndex] = true;
if (assign(compatibility, stackIndex + 1, usedIngredients)) return true;
usedIngredients[ingredientIndex] = false;
}
return false;
}
}
@@ -4,6 +4,7 @@ import com.mojang.serialization.Codec;
import com.mojang.serialization.MapCodec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import fr.koka99cab.sanctuary26.itsalive.culinary.CulinaryAppliance;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import net.minecraft.network.RegistryFriendlyByteBuf;
@@ -79,6 +80,23 @@ public final class CulinaryProcessRecipe implements Recipe<CulinaryRecipeInput>
return assignIngredient(0, stacks, new int[stacks.size()], assignment) ? assignment : null;
}
/** True when every occupied slot can still become part of this recipe. */
public boolean acceptsPartial(List<ItemStack> stacks) {
List<ItemStack> occupied = new ArrayList<>();
for (ItemStack stack : stacks) {
if (!stack.isEmpty()) occupied.add(stack);
}
if (occupied.size() > ingredients.size()) return false;
boolean[][] compatibility = new boolean[occupied.size()][ingredients.size()];
for (int stackIndex = 0; stackIndex < occupied.size(); stackIndex++) {
for (int ingredientIndex = 0; ingredientIndex < ingredients.size(); ingredientIndex++) {
compatibility[stackIndex][ingredientIndex] =
ingredients.get(ingredientIndex).test(occupied.get(stackIndex));
}
}
return CulinaryPartialMatching.canAssignDistinctIngredients(compatibility);
}
private int[] matchingDistinctSlots(List<ItemStack> stacks) {
int[] assignment = new int[ingredients.size()];
Arrays.fill(assignment, -1);
@@ -2,6 +2,7 @@ package fr.koka99cab.sanctuary26.itsalive.culinary.recipe;
import fr.koka99cab.sanctuary26.itsalive.ItsAliveMod;
import fr.koka99cab.sanctuary26.itsalive.culinary.CulinaryAppliance;
import java.util.List;
import java.util.Optional;
import net.minecraft.core.Registry;
import net.minecraft.core.registries.BuiltInRegistries;
@@ -79,4 +80,18 @@ public final class CulinaryRecipeTypes {
.anyMatch(recipe -> recipe.appliance() == appliance
&& recipe.ingredients().stream().anyMatch(ingredient -> ingredient.test(stack)));
}
/** True when all supplied stacks form a valid, shapeless prefix of one loaded recipe. */
public static boolean acceptsPartial(
ServerLevel level,
CulinaryAppliance appliance,
List<ItemStack> stacks
) {
if (type == null) return false;
return level.getServer().getRecipeManager().getRecipes().stream()
.map(RecipeHolder::value)
.filter(CulinaryProcessRecipe.class::isInstance)
.map(CulinaryProcessRecipe.class::cast)
.anyMatch(recipe -> recipe.appliance() == appliance && recipe.acceptsPartial(stacks));
}
}
@@ -1,10 +1,10 @@
{
"variants": {
"facing=down": { "model": "itsalive:block/fermentation_barrel", "x": 180 },
"facing=east": { "model": "itsalive:block/fermentation_barrel", "x": 90, "y": 90 },
"facing=north": { "model": "itsalive:block/fermentation_barrel", "x": 90 },
"facing=south": { "model": "itsalive:block/fermentation_barrel", "x": 90, "y": 180 },
"facing=up": { "model": "itsalive:block/fermentation_barrel" },
"facing=west": { "model": "itsalive:block/fermentation_barrel", "x": 90, "y": 270 }
}
"multipart": [
{ "when": { "facing": "down" }, "apply": { "model": "itsalive:block/fermentation_barrel", "x": 180 } },
{ "when": { "facing": "east" }, "apply": { "model": "itsalive:block/fermentation_barrel", "x": 90, "y": 90 } },
{ "when": { "facing": "north" }, "apply": { "model": "itsalive:block/fermentation_barrel", "x": 90 } },
{ "when": { "facing": "south" }, "apply": { "model": "itsalive:block/fermentation_barrel", "x": 90, "y": 180 } },
{ "when": { "facing": "up" }, "apply": { "model": "itsalive:block/fermentation_barrel" } },
{ "when": { "facing": "west" }, "apply": { "model": "itsalive:block/fermentation_barrel", "x": 90, "y": 270 } }
]
}
@@ -0,0 +1,23 @@
package fr.koka99cab.sanctuary26.itsalive.culinary.recipe;
/** Pure recipe-prefix smoke for hopper-fed culinary appliances. */
public final class CulinaryPartialMatchingSmoke {
private CulinaryPartialMatchingSmoke() {
}
public static void main(String[] args) {
expect(true, new boolean[0][0], "empty prefix");
expect(true, new boolean[][] {{true, false, false}}, "single valid ingredient");
expect(true, new boolean[][] {{false, true}, {true, false}}, "shapeless assignment");
expect(false, new boolean[][] {{true, false}, {true, false}}, "duplicate ingredient");
expect(false, new boolean[][] {{false, false}}, "unrelated ingredient");
expect(false, new boolean[][] {{true}, {true}}, "too many ingredients");
}
private static void expect(boolean expected, boolean[][] compatibility, String label) {
boolean actual = CulinaryPartialMatching.canAssignDistinctIngredients(compatibility);
if (actual != expected) {
throw new AssertionError(label + ": expected " + expected + ", got " + actual);
}
}
}
@@ -0,0 +1,49 @@
{
"schema_version": 1,
"source": {
"pack_version": "26.2.0-alpha.197",
"modules": {
"itsalive": "0.0.0-alpha.29",
"sanctuary": "0.0.0-alpha.113"
}
},
"target": {
"pack_version": "26.2.0-alpha.198",
"modules": {
"itsalive": "0.0.0-alpha.30",
"sanctuary": "0.0.0-alpha.114"
}
},
"fermentation_barrel": {
"id": "itsalive:fermentation_barrel",
"input_slots": 4,
"maximum_units_per_input_slot": 1,
"loaded_recipe_prefix_filter": true,
"duplicate_ingredients_rejected_unless_recipe_requires_them": true,
"further_input_rejected_when_recipe_complete": true,
"observer_state": {
"property": "contents",
"empty": 0,
"accepted_ingredients": "1-4",
"result_ready": 5,
"vanilla_neighbor_updates": true,
"progress_tick_updates": false
}
},
"inventory_shift_click": {
"owner": "sanctuary",
"hotbar_to_all_unlocked_storage_rows": true,
"storage_and_overflow_to_hotbar_only": true,
"shift_double_click_respects_inventory_groups": true
},
"distribution": {
"packwiz_index_refreshed": true
},
"compatibility": {
"itsalive_data_version": 1,
"itsalive_network_protocol": 3,
"existing_input_stacks_preserved": true,
"save_migration_required": false,
"registry_ids_changed": false
}
}
+12 -12
View File
@@ -14,15 +14,15 @@ hash = "41f4f4c88c71d0a23446d5014f7407d55ad477441847099cad24492b032335a0"
[[files]]
file = "mods/ambiance-0.0.0-alpha.11.jar"
hash = "d8a2f1936e9c43ad36e3ffab5f791315970a66f374bf3f6fc387d56107203898"
hash = "70c23a4e4059eaf52f99e4a31585169c6599b4e33ad7554cddbbec536096cc64"
[[files]]
file = "mods/anotherworld-0.0.0-alpha.49.jar"
hash = "992c86f16136a9c013888ced20d863528d4f6c48244bb1d342c189b3ac4ad739"
hash = "69f6c36628503f5abf04ec3f9e8351d7b3e5468829573733590641a9078760d0"
[[files]]
file = "mods/events-0.0.0-alpha.0.jar"
hash = "a637271a50be68666a9eb86bd4a817f19d0592f81127b96746377cb2558394e0"
hash = "2cd283f8b85055a405947a41325f9780b98046b659a24a95c3279d96c97205d5"
[[files]]
file = "mods/fabric-api.pw.toml"
@@ -31,7 +31,7 @@ metafile = true
[[files]]
file = "mods/iliketomoveit-0.0.0-alpha.13.jar"
hash = "d2b1c2c0769a3da4a7374ae8f39311531b48683dea277f8c3f084d77c50b795b"
hash = "b1b33c5fe7089b8d1baac4ce5fbd0ee386def1456b71af6f761d6ae800e61690"
[[files]]
file = "mods/iris.pw.toml"
@@ -39,8 +39,8 @@ hash = "84a3cef87679c0d21dceb4641167dc0368b9a3593698f6a3632485e9b3a8d5da"
metafile = true
[[files]]
file = "mods/itsalive-0.0.0-alpha.29.jar"
hash = "2ccfc7b6aead6d00f5dc84a4627acba9ab9da70229c066799089dd98de826b90"
file = "mods/itsalive-0.0.0-alpha.30.jar"
hash = "57a7a91b47c5648710fa165a894c785c5958afe07f7d7f4e3ef4d0cc38cacd8b"
[[files]]
file = "mods/jei.pw.toml"
@@ -49,23 +49,23 @@ metafile = true
[[files]]
file = "mods/masterkey-0.0.0-alpha.8.jar"
hash = "11f885417f45cc6fa752f561ecb475ad43a99071f88fd6a0f0521a292a93642e"
hash = "90803d3a67065ee02cca4fc32c8f18301e93e1ff2c4c8002feb5f28c7ec014da"
[[files]]
file = "mods/onlyfun-0.0.0-alpha.19.jar"
hash = "d9b5e7bfd96aac0bc2cfc6394249c86c74ad58d6685a3af37bb090682377a23a"
hash = "86c7f88ec348f678cb873d2386df38ca300928f4b0e7c350cae722ba42389986"
[[files]]
file = "mods/ouch-0.0.0-alpha.8.jar"
hash = "4152c0ecbdde7450c49c7e28367efa02ae666bb29dbb247ac90da3393e6485a0"
hash = "bd57342e13832fe67e7abeb0f2768170552fd868668a6f6940cd73f57dd7b37c"
[[files]]
file = "mods/redstoner-0.0.0-alpha.10.jar"
hash = "73262d1f88f820935ce3e59e1884bce66c6f1883bf5becbc9d2136a64d862011"
hash = "2259c2a982c14d29f27c7a0908aeee11b7156b2823202a200aa89af24703eb23"
[[files]]
file = "mods/sanctuary-0.0.0-alpha.113.jar"
hash = "31155aad4c2030a5ede42b1b1d16cba191eaa0ade65051ff4d3973bf2db94c42"
file = "mods/sanctuary-0.0.0-alpha.114.jar"
hash = "7e45eb1a6756ed9a95d79d794b7c2e7a6918388fd9e5286160cd890d1dfaf0f6"
[[files]]
file = "mods/simple-voice-chat.pw.toml"
+2 -2
View File
@@ -1,12 +1,12 @@
name = "Sanctuary 26.2"
author = "KOKA99CAB"
version = "26.2.0-alpha.197"
version = "26.2.0-alpha.198"
pack-format = "packwiz:1.1.0"
[index]
file = "index.toml"
hash-format = "sha256"
hash = "c191cd8a9c30d94babee9696a0d67268368f943c44fa26da45e1ba9996db03c7"
hash = "55cce7f3d86dbb8546626ea5f3ed5596efa656ab41f528d8b7ed963470b81219"
[versions]
fabric = "0.19.3"
+1 -1
View File
@@ -3,7 +3,7 @@
"game": "minecraft",
"name": "Sanctuary 26.2",
"summary": "Sanctuary 26.2 — progression, exploration, worldgen and multiplayer systems by KOKA99CAB.",
"versionId": "26.2.0-alpha.197",
"versionId": "26.2.0-alpha.198",
"files": [
{
"path": "mods/fabric-api-0.158.0+26.2.jar",
+2 -2
View File
@@ -1,6 +1,6 @@
{
"schema_version": 1,
"pack_version": "26.2.0-alpha.197",
"pack_version": "26.2.0-alpha.198",
"release_channel": "alpha",
"minecraft_version": "26.2",
"fabric_loader_version": "0.19.3",
@@ -9,7 +9,7 @@
"project_id": "FgOqyAXd",
"project_slug": "sanctuary-koka99",
"project_url": "https://modrinth.com/modpack/sanctuary-koka99",
"primary_file": "sanctuary-26.2.0-alpha.197.mrpack",
"primary_file": "sanctuary-26.2.0-alpha.198.mrpack",
"shared_client_server_pack": true,
"update_owner": "packwiz-installer",
"check_on_launch": true,
-1
View File
@@ -3097,4 +3097,3 @@ tasks.named("check") {
dependsOn tasks.named("celestialGraphSmoke")
dependsOn tasks.named("questRequestModelSmoke")
}