From b5a30d294d7ea95095afa311d789365c84fc1f7d Mon Sep 17 00:00:00 2001 From: koka Date: Thu, 27 Aug 2026 17:41:25 +0200 Subject: [PATCH] fix(anotherworld): form hollow 3x3x3 safe vault --- CHANGELOG.md | 11 +++ README.md | 6 +- anotherworld/build.gradle | 43 ++++++--- .../anotherworld/block/SafeVaultBlock.java | 2 +- .../block/entity/SafeVaultBlockEntity.java | 6 +- .../anotherworld/vault/SafeVaultMenu.java | 2 +- .../anotherworld/vault/VaultData.java | 4 +- .../anotherworld/vault/VaultPart.java | 52 ++++++++--- .../anotherworld/vault/VaultRules.java | 17 +++- .../anotherworld/vault/VaultService.java | 21 +++-- .../anotherworld/vault/VaultStructure.java | 75 ++++++++++----- .../src/main/resources/fabric.mod.json | 2 +- .../anotherworld/vault/VaultRulesSmoke.java | 38 ++++++-- build.gradle | 93 ++++++++++++++++--- gradle.properties | 2 +- .../anotherworld-safe-vault-cube-alpha53.json | 42 +++++++++ 16 files changed, 323 insertions(+), 93 deletions(-) create mode 100644 pack/migrations/anotherworld-safe-vault-cube-alpha53.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 2385df5..0138930 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,17 @@ 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. +## En développement — coffre-fort creux 3 × 3 × 3 + +Module modifié : **Another World `0.0.0-alpha.53`**. Le pack distribué reste en **`26.2.0-alpha.204`**. + +- remplace le mur de neuf Safe Blocks par une coque creuse de 26 blocs disposés en 3 × 3 × 3 ; +- laisse le bloc central entièrement libre afin de pouvoir y cacher n’importe quel bloc ; +- refuse les murs 3 × 3, les cubes pleins et toute structure à laquelle un Safe Block supplémentaire est attaché ; +- répartit les textures 48×48 sur chacune des faces extérieures du cube, avec la façade, le dessus, le dessous et les côtés/arrière ; +- conserve les anciennes valeurs de blockstate pour permettre le chargement et le démontage des murs existants, qui doivent être reconstruits en cube ; +- préserve les comptes, les 27 emplacements de stockage, les soldes et le protocole réseau ; Another World passe à la donnée v4 et `safe_vaults` à la donnée v3. + ## `26.2.0-alpha.204` Module modifié : **Another World `0.0.0-alpha.52`**. Les neuf autres modules conservent leur version issue de l’alpha.203. diff --git a/README.md b/README.md index ad10f48..6d63833 100644 --- a/README.md +++ b/README.md @@ -22,7 +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 distribué : **`26.2.0-alpha.204`**. Another World passe en **`0.0.0-alpha.52`** ; les neuf autres modules conservent leur version de l’alpha.203. +Version courante du pack distribué : **`26.2.0-alpha.204`**. Dans le workspace, la correction cubique du Safe Block porte Another World en développement à **`0.0.0-alpha.53`** sans produire une nouvelle version du pack. + +Another World alpha.53 exige une coque creuse de **3 × 3 × 3**, soit 26 Safe Blocks appartenant au même joueur. Le centre est libre et peut contenir un bloc caché. Les quatre images 48×48 se répartissent sur les faces extérieures du cube : façade, dessus, dessous et côtés/arrière. Un ancien mur 3 × 3 ne s’active plus et doit être démonté puis reconstruit ; les comptes et soldes existants restent conservés. L’alpha.204 finalise le Safe Block vertical et actualise JEI en **`30.27.0.188`** : sa texture individuelle 16×16 est chargée avant formation, les structures incomplètes 3 × 1 sont refusées et le CTM 48×48 exige les neuf blocs. L’interface monétaire affiche désormais correctement l’émeraude et place l’inventaire étendu sous les trois lignes de monnaie. @@ -30,7 +32,7 @@ L’alpha.203 porte Another World en **`0.0.0-alpha.51`** et JEI en **`30.27.0.1 L’alpha.202 permet de descendre par un ascenseur de Waystones vers l’étage immédiatement inférieur encore inconnu, sans ouvrir la montée ni les trajets ordinaires aux destinations non découvertes. Le Shop et le marché noir comptent et consomment désormais les rubis et saphirs de toutes les rangées débloquées, y compris l’overflow. Le livre culinaire peut enfin être posé sur un pupitre vanilla pour partager le catalogue de recettes de son propriétaire ; retiré du pupitre, il redevient personnel. -Le coffre-fort joueur utilise neuf blocs Another World disposés en mur vertical de 3 × 3. Il conserve les rubis, saphirs et émeraudes dans un compte lié au joueur et les rend disponibles aux transactions du Shop même lorsque la structure est déchargée. Une foreuse en titane maintenue pendant 60 secondes permet de piller 20 % du contenu d’un propriétaire connecté, qui reçoit immédiatement une alerte ; une réussite consomme la foreuse et protège ensuite le coffre pendant 24 heures. +Le coffre-fort joueur utilise 26 blocs Another World disposés en coque creuse de 3 × 3 × 3 autour d’un centre libre. Il conserve les rubis, saphirs et émeraudes dans un compte lié au joueur et les rend disponibles aux transactions du Shop même lorsque la structure est déchargée. Une foreuse en titane maintenue pendant 60 secondes permet de piller 20 % du contenu d’un propriétaire connecté, qui reçoit immédiatement une alerte ; une réussite consomme la foreuse et protège ensuite le coffre pendant 24 heures. L’alpha.201 rend les demandes communautaires réellement terminables. Le demandeur dépose la récompense lors de la publication, le preneur confirme sans commande après avoir lu les détails, KILL progresse automatiquement, BRING livre les objets au demandeur et PAY est confirmé par le joueur désigné. La récompense séquestrée est ensuite expédiée une seule fois dans la Mailbox du preneur. Le pseudonyme conserve la couleur du joueur et le panneau rose adopte une palette plus claire et poudrée. diff --git a/anotherworld/build.gradle b/anotherworld/build.gradle index 9c0e0c7..462deae 100644 --- a/anotherworld/build.gradle +++ b/anotherworld/build.gradle @@ -9,7 +9,7 @@ sourceSets.main.resources.srcDir(generatedResources) tasks.register("vaultRulesSmoke", JavaExec) { group = "verification" - description = "Checks the 3x3 vault, one-minute raid, 20% loot and 24-hour protection rules." + description = "Checks the hollow 3x3x3 vault, one-minute raid, 20% loot and 24-hour protection rules." dependsOn tasks.named("testClasses") classpath = sourceSets.test.runtimeClasspath mainClass = "fr.koka99cab.sanctuary26.anotherworld.vault.VaultRulesSmoke" @@ -177,13 +177,24 @@ tasks.register("generateAnotherWorldResources") { // Mirror, creative-only rainbow blocks, gems and first-pass metal art. simpleBlock("mirror", "minecraft:block/cube_all", "anotherworld:block/mirror_glass") - def vaultParts = [ + def vaultFaceParts = [ [id: "bottom_left", column: 0, row: 0], [id: "bottom_center", column: 1, row: 0], [id: "bottom_right", column: 2, row: 0], [id: "middle_left", column: 0, row: 1], [id: "center", column: 1, row: 1], [id: "middle_right", column: 2, row: 1], [id: "top_left", column: 0, row: 2], [id: "top_center", column: 1, row: 2], [id: "top_right", column: 2, row: 2] ] + def vaultParts = [] + [[prefix: "back", depth: 0], [prefix: null, depth: 1], [prefix: "front", depth: 2]].each { layer -> + vaultFaceParts.each { facePart -> + vaultParts.add([ + id: layer.prefix == null ? facePart.id : "${layer.prefix}_${facePart.id}", + column: facePart.column, + row: facePart.row, + depth: layer.depth + ]) + } + } def safeMultipart = [[when: [formed: "false"], apply: [model: "anotherworld:block/safe_block_unformed"]]] [north: 0, east: 90, south: 180, west: 270].each { facing, rotation -> vaultParts.each { part -> @@ -203,8 +214,10 @@ tasks.register("generateAnotherWorldResources") { vaultParts.each { part -> def frontUv = segmentUv(part.column as int, part.row as int) def backUv = segmentUv(2 - (part.column as int), part.row as int) - def sideUv = segmentUv(1, part.row as int) - def topUv = segmentUv(part.column as int, 1) + def westUv = segmentUv(2 - (part.depth as int), part.row as int) + def eastUv = segmentUv(part.depth as int, part.row as int) + def topUv = segmentUv(part.column as int, part.depth as int) + def bottomUv = segmentUv(part.column as int, 2 - (part.depth as int)) writeJson("assets/anotherworld/models/block/safe_block/${part.id}.json", [ textures: [ particle: "anotherworld:block/safe_vault/front", @@ -214,12 +227,12 @@ tasks.register("generateAnotherWorldResources") { side: "anotherworld:block/safe_vault/side" ], elements: [[from: [0, 0, 0], to: [16, 16, 16], faces: [ - down: [uv: topUv, texture: "#bottom", cullface: "down"], + down: [uv: bottomUv, texture: "#bottom", cullface: "down"], up: [uv: topUv, texture: "#top", cullface: "up"], north: [uv: frontUv, texture: "#front", cullface: "north"], south: [uv: backUv, texture: "#side", cullface: "south"], - west: [uv: sideUv, texture: "#side", cullface: "west"], - east: [uv: sideUv, texture: "#side", cullface: "east"] + west: [uv: westUv, texture: "#side", cullface: "west"], + east: [uv: eastUv, texture: "#side", cullface: "east"] ]]] ]) } @@ -1622,9 +1635,9 @@ tasks.register("generateAnotherWorldResources") { "container.anotherworld.safe_vault": "Coffre-fort", "tooltip.anotherworld.titanium_drill.use": "Maintenir sur un coffre-fort ennemi pour le forer.", "tooltip.anotherworld.titanium_drill.consumed": "Consommée après un pillage réussi.", - "message.anotherworld.safe_vault.incomplete": "Le coffre-fort exige exactement 9 blocs appartenant au même joueur, posés en mur vertical de 3 × 3.", + "message.anotherworld.safe_vault.incomplete": "Le coffre-fort exige exactement 26 blocs appartenant au même joueur, disposés en coque creuse de 3 × 3 × 3 avec un centre libre.", "message.anotherworld.safe_vault.locked": "Ce coffre-fort appartient à un autre joueur.", - "message.anotherworld.safe_vault.formed": "Coffre-fort vertical 3 × 3 lié à vous en %s, %s, %s.", + "message.anotherworld.safe_vault.formed": "Coffre-fort creux 3 × 3 × 3 lié à vous en %s, %s, %s.", "message.anotherworld.safe_vault.deposit_failed": "Impossible de déposer %s : inventaire vide ou coffre-fort plein.", "message.anotherworld.safe_vault.withdraw_failed": "Impossible de retirer %s : solde vide ou inventaire plein.", "message.anotherworld.safe_vault.already_owned": "Vous possédez déjà un coffre-fort actif.", @@ -1646,9 +1659,9 @@ tasks.register("generateAnotherWorldResources") { "container.anotherworld.safe_vault": "Сейф", "tooltip.anotherworld.titanium_drill.use": "Удерживайте на чужом сейфе, чтобы просверлить его.", "tooltip.anotherworld.titanium_drill.consumed": "Расходуется после успешного ограбления.", - "message.anotherworld.safe_vault.incomplete": "Сейф требует вертикальную стену 3 × 3 из 9 блоков одного владельца.", + "message.anotherworld.safe_vault.incomplete": "Сейф требует полую оболочку 3 × 3 × 3 из 26 блоков одного владельца со свободным центром.", "message.anotherworld.safe_vault.locked": "Этот сейф принадлежит другому игроку.", - "message.anotherworld.safe_vault.formed": "Вертикальный сейф 3 × 3 привязан к вам в точке %s, %s, %s.", + "message.anotherworld.safe_vault.formed": "Полый сейф 3 × 3 × 3 привязан к вам в точке %s, %s, %s.", "message.anotherworld.safe_vault.deposit_failed": "Не удалось внести %s: предметов нет или сейф заполнен.", "message.anotherworld.safe_vault.withdraw_failed": "Не удалось забрать %s: баланс пуст или инвентарь заполнен.", "message.anotherworld.safe_vault.already_owned": "У вас уже есть активный сейф.", @@ -1670,9 +1683,9 @@ tasks.register("generateAnotherWorldResources") { "container.anotherworld.safe_vault": "Safe", "tooltip.anotherworld.titanium_drill.use": "Hold on an enemy safe to drill it.", "tooltip.anotherworld.titanium_drill.consumed": "Consumed after a successful raid.", - "message.anotherworld.safe_vault.incomplete": "A safe requires an upright 3 × 3 wall of exactly 9 blocks owned by one player.", + "message.anotherworld.safe_vault.incomplete": "A safe requires a hollow 3 × 3 × 3 shell of exactly 26 blocks owned by one player, with a free center.", "message.anotherworld.safe_vault.locked": "This safe belongs to another player.", - "message.anotherworld.safe_vault.formed": "Upright 3 × 3 safe linked to you at %s, %s, %s.", + "message.anotherworld.safe_vault.formed": "Hollow 3 × 3 × 3 safe linked to you at %s, %s, %s.", "message.anotherworld.safe_vault.deposit_failed": "Could not deposit %s: none available or the safe is full.", "message.anotherworld.safe_vault.withdraw_failed": "Could not withdraw %s: the balance is empty or inventory is full.", "message.anotherworld.safe_vault.already_owned": "You already own an active safe.", @@ -1938,7 +1951,7 @@ tasks.register("verifyAnotherWorldCatalog") { "src/main/resources/assets/redstoner/textures/item/weather_gauge.png", "src/main/resources/data/redstoner/recipe/weather_gauge.json" ] - if (project.version.toString() != "0.0.0-alpha.52" + if (project.version.toString() != "0.0.0-alpha.53" || sulfurMigration.target?.modules?.anotherworld != "0.0.0-alpha.44" || sulfurMigration.sulfur_caves?.sanctuary?.dripstone_cell_period != 2 || !javaText.contains('Identifier.parse("sanctuary:sulfur_caves")') @@ -3255,7 +3268,7 @@ tasks.register("verifyClimateBiomeMixer") { "pack/migrations/26.2.0-alpha.188-beta-climate-provinces-to-alpha.189.json")) def mixer = file( "src/main/java/fr/koka99cab/sanctuary26/anotherworld/mixin/MultiNoiseBiomeSourceParameterListPresetMixin.java").text - if (project.version.toString() != "0.0.0-alpha.52" + if (project.version.toString() != "0.0.0-alpha.53" || migration.source?.pack_version != "26.2.0-alpha.188" || migration.target?.pack_version != "26.2.0-alpha.189" || migration.target?.modules?.anotherworld != "0.0.0-alpha.48" diff --git a/anotherworld/src/main/java/fr/koka99cab/sanctuary26/anotherworld/block/SafeVaultBlock.java b/anotherworld/src/main/java/fr/koka99cab/sanctuary26/anotherworld/block/SafeVaultBlock.java index 323613d..416a45e 100644 --- a/anotherworld/src/main/java/fr/koka99cab/sanctuary26/anotherworld/block/SafeVaultBlock.java +++ b/anotherworld/src/main/java/fr/koka99cab/sanctuary26/anotherworld/block/SafeVaultBlock.java @@ -108,7 +108,7 @@ public final class SafeVaultBlock extends BaseEntityBlock { player.sendSystemMessage(Component.translatable("message.anotherworld.safe_vault.locked")); return InteractionResult.CONSUME; } - if (level.getBlockEntity(formed.center()) instanceof SafeVaultBlockEntity controller) { + if (level.getBlockEntity(formed.controller()) instanceof SafeVaultBlockEntity controller) { player.openMenu(controller); return InteractionResult.SUCCESS_SERVER; } diff --git a/anotherworld/src/main/java/fr/koka99cab/sanctuary26/anotherworld/block/entity/SafeVaultBlockEntity.java b/anotherworld/src/main/java/fr/koka99cab/sanctuary26/anotherworld/block/entity/SafeVaultBlockEntity.java index 197a3b3..34bd771 100644 --- a/anotherworld/src/main/java/fr/koka99cab/sanctuary26/anotherworld/block/entity/SafeVaultBlockEntity.java +++ b/anotherworld/src/main/java/fr/koka99cab/sanctuary26/anotherworld/block/entity/SafeVaultBlockEntity.java @@ -3,6 +3,7 @@ package fr.koka99cab.sanctuary26.anotherworld.block.entity; import fr.koka99cab.sanctuary26.anotherworld.registry.AnotherWorldBlockEntities; import fr.koka99cab.sanctuary26.anotherworld.vault.VaultData; import fr.koka99cab.sanctuary26.anotherworld.vault.SafeVaultMenu; +import fr.koka99cab.sanctuary26.anotherworld.vault.VaultStructure; import java.util.UUID; import net.minecraft.core.BlockPos; import net.minecraft.core.UUIDUtil; @@ -54,7 +55,10 @@ public final class SafeVaultBlockEntity extends BlockEntity implements MenuProvi @Override public AbstractContainerMenu createMenu(int containerId, Inventory inventory, Player player) { if (owner == null || !owner.equals(player.getUUID()) || !(level instanceof ServerLevel serverLevel)) return null; + VaultStructure.Formed formed = VaultStructure.find(serverLevel, getBlockPos()) + .filter(candidate -> candidate.owner().equals(owner)).orElse(null); + if (formed == null || !formed.controller().equals(getBlockPos())) return null; return new SafeVaultMenu(containerId, inventory, VaultData.get(serverLevel.getServer()), - serverLevel, getBlockPos(), owner); + serverLevel, formed.center(), owner); } } diff --git a/anotherworld/src/main/java/fr/koka99cab/sanctuary26/anotherworld/vault/SafeVaultMenu.java b/anotherworld/src/main/java/fr/koka99cab/sanctuary26/anotherworld/vault/SafeVaultMenu.java index ff732f7..4fb0094 100644 --- a/anotherworld/src/main/java/fr/koka99cab/sanctuary26/anotherworld/vault/SafeVaultMenu.java +++ b/anotherworld/src/main/java/fr/koka99cab/sanctuary26/anotherworld/vault/SafeVaultMenu.java @@ -104,7 +104,7 @@ public final class SafeVaultMenu extends AbstractContainerMenu { return false; } return vaultData.isRegistered(serverLevel, center, owner) - && VaultStructure.find(serverLevel, center) + && VaultStructure.findAtCenter(serverLevel, center) .filter(formed -> formed.center().equals(center) && formed.owner().equals(owner)).isPresent(); } diff --git a/anotherworld/src/main/java/fr/koka99cab/sanctuary26/anotherworld/vault/VaultData.java b/anotherworld/src/main/java/fr/koka99cab/sanctuary26/anotherworld/vault/VaultData.java index b5cabd9..ee47baf 100644 --- a/anotherworld/src/main/java/fr/koka99cab/sanctuary26/anotherworld/vault/VaultData.java +++ b/anotherworld/src/main/java/fr/koka99cab/sanctuary26/anotherworld/vault/VaultData.java @@ -26,7 +26,7 @@ import net.minecraft.world.level.saveddata.SavedDataType; /** Global player-linked vault inventory, available to the Shop even when its structure is unloaded. */ public final class VaultData extends SavedData { public static final String SAVE_KEY = "safe_vaults"; - public static final int DATA_VERSION = 2; + public static final int DATA_VERSION = 3; private static final Identifier RUBY = Identifier.fromNamespaceAndPath("anotherworld", "ruby"); private static final Identifier SAPPHIRE = Identifier.fromNamespaceAndPath("anotherworld", "sapphire"); private static final Identifier EMERALD = Identifier.withDefaultNamespace("emerald"); @@ -294,7 +294,7 @@ public final class VaultData extends SavedData { || player.distanceToSqr(center.getX() + 0.5D, center.getY() + 0.5D, center.getZ() + 0.5D) > 64.0D) return false; return data.isRegistered(level, center, owner) - && VaultStructure.find(level, center).filter(formed -> formed.center().equals(center)).isPresent(); + && VaultStructure.findAtCenter(level, center).filter(formed -> formed.center().equals(center)).isPresent(); } @Override public void clearContent() { data.items(owner).clear(); setChanged(); } @Override public boolean canPlaceItem(int slot, ItemStack stack) { return isCurrency(stack); } diff --git a/anotherworld/src/main/java/fr/koka99cab/sanctuary26/anotherworld/vault/VaultPart.java b/anotherworld/src/main/java/fr/koka99cab/sanctuary26/anotherworld/vault/VaultPart.java index bfb9b76..c7b2c38 100644 --- a/anotherworld/src/main/java/fr/koka99cab/sanctuary26/anotherworld/vault/VaultPart.java +++ b/anotherworld/src/main/java/fr/koka99cab/sanctuary26/anotherworld/vault/VaultPart.java @@ -2,26 +2,46 @@ package fr.koka99cab.sanctuary26.anotherworld.vault; import net.minecraft.util.StringRepresentable; -/** Stable position of one segment in the front-facing 3x3 vault wall. */ +/** Stable position of one segment in the front-facing 3x3x3 vault cube. */ public enum VaultPart implements StringRepresentable { - BOTTOM_LEFT("bottom_left", 0, 0), - BOTTOM_CENTER("bottom_center", 1, 0), - BOTTOM_RIGHT("bottom_right", 2, 0), - MIDDLE_LEFT("middle_left", 0, 1), - CENTER("center", 1, 1), - MIDDLE_RIGHT("middle_right", 2, 1), - TOP_LEFT("top_left", 0, 2), - TOP_CENTER("top_center", 1, 2), - TOP_RIGHT("top_right", 2, 2); + BACK_BOTTOM_LEFT("back_bottom_left", 0, 0, 0), + BACK_BOTTOM_CENTER("back_bottom_center", 1, 0, 0), + BACK_BOTTOM_RIGHT("back_bottom_right", 2, 0, 0), + BACK_MIDDLE_LEFT("back_middle_left", 0, 1, 0), + BACK_CENTER("back_center", 1, 1, 0), + BACK_MIDDLE_RIGHT("back_middle_right", 2, 1, 0), + BACK_TOP_LEFT("back_top_left", 0, 2, 0), + BACK_TOP_CENTER("back_top_center", 1, 2, 0), + BACK_TOP_RIGHT("back_top_right", 2, 2, 0), + BOTTOM_LEFT("bottom_left", 0, 0, 1), + BOTTOM_CENTER("bottom_center", 1, 0, 1), + BOTTOM_RIGHT("bottom_right", 2, 0, 1), + MIDDLE_LEFT("middle_left", 0, 1, 1), + CENTER("center", 1, 1, 1), + MIDDLE_RIGHT("middle_right", 2, 1, 1), + TOP_LEFT("top_left", 0, 2, 1), + TOP_CENTER("top_center", 1, 2, 1), + TOP_RIGHT("top_right", 2, 2, 1), + FRONT_BOTTOM_LEFT("front_bottom_left", 0, 0, 2), + FRONT_BOTTOM_CENTER("front_bottom_center", 1, 0, 2), + FRONT_BOTTOM_RIGHT("front_bottom_right", 2, 0, 2), + FRONT_MIDDLE_LEFT("front_middle_left", 0, 1, 2), + FRONT_CENTER("front_center", 1, 1, 2), + FRONT_MIDDLE_RIGHT("front_middle_right", 2, 1, 2), + FRONT_TOP_LEFT("front_top_left", 0, 2, 2), + FRONT_TOP_CENTER("front_top_center", 1, 2, 2), + FRONT_TOP_RIGHT("front_top_right", 2, 2, 2); private final String serializedName; private final int column; private final int row; + private final int depth; - VaultPart(String serializedName, int column, int row) { + VaultPart(String serializedName, int column, int row, int depth) { this.serializedName = serializedName; this.column = column; this.row = row; + this.depth = depth; } public int column() { @@ -32,11 +52,15 @@ public enum VaultPart implements StringRepresentable { return row; } - public static VaultPart at(int column, int row) { + public int depth() { + return depth; + } + + public static VaultPart at(int column, int row, int depth) { for (VaultPart part : values()) { - if (part.column == column && part.row == row) return part; + if (part.column == column && part.row == row && part.depth == depth) return part; } - throw new IllegalArgumentException("Invalid vault part: " + column + ", " + row); + throw new IllegalArgumentException("Invalid vault part: " + column + ", " + row + ", " + depth); } @Override diff --git a/anotherworld/src/main/java/fr/koka99cab/sanctuary26/anotherworld/vault/VaultRules.java b/anotherworld/src/main/java/fr/koka99cab/sanctuary26/anotherworld/vault/VaultRules.java index e2d4cc9..0b9ea79 100644 --- a/anotherworld/src/main/java/fr/koka99cab/sanctuary26/anotherworld/vault/VaultRules.java +++ b/anotherworld/src/main/java/fr/koka99cab/sanctuary26/anotherworld/vault/VaultRules.java @@ -3,9 +3,14 @@ package fr.koka99cab.sanctuary26.anotherworld.vault; /** Pure balancing rules shared by runtime code and smoke tests. */ public final class VaultRules { public static final int STRUCTURE_WIDTH = 3; + public static final int STRUCTURE_DEPTH = 3; public static final int STRUCTURE_RADIUS = 1; - public static final int STRUCTURE_BLOCKS = STRUCTURE_WIDTH * STRUCTURE_WIDTH; - public static final int COMPLETE_STRUCTURE_MASK = (1 << STRUCTURE_BLOCKS) - 1; + public static final int STRUCTURE_POSITIONS = STRUCTURE_WIDTH * STRUCTURE_WIDTH * STRUCTURE_DEPTH; + public static final int STRUCTURE_BLOCKS = STRUCTURE_POSITIONS - 1; + public static final int CENTER_PART_INDEX = STRUCTURE_RADIUS * STRUCTURE_WIDTH * STRUCTURE_WIDTH + + STRUCTURE_RADIUS * STRUCTURE_WIDTH + STRUCTURE_RADIUS; + public static final int FULL_STRUCTURE_MASK = (1 << STRUCTURE_POSITIONS) - 1; + public static final int COMPLETE_STRUCTURE_MASK = FULL_STRUCTURE_MASK & ~(1 << CENTER_PART_INDEX); public static final int STORAGE_SLOTS = 27; public static final int CURRENCY_SLOTS = 3; public static final int BUTTONS_PER_CURRENCY = 4; @@ -30,6 +35,14 @@ public final class VaultRules { return partMask == COMPLETE_STRUCTURE_MASK; } + public static int partIndex(int column, int row, int depth) { + if (column < 0 || column >= STRUCTURE_WIDTH || row < 0 || row >= STRUCTURE_WIDTH + || depth < 0 || depth >= STRUCTURE_DEPTH) { + throw new IllegalArgumentException("Invalid vault coordinates: " + column + ", " + row + ", " + depth); + } + return depth * STRUCTURE_WIDTH * STRUCTURE_WIDTH + row * STRUCTURE_WIDTH + column; + } + public static int buttonCurrency(int buttonId) { return validButton(buttonId) ? buttonId / BUTTONS_PER_CURRENCY : -1; } diff --git a/anotherworld/src/main/java/fr/koka99cab/sanctuary26/anotherworld/vault/VaultService.java b/anotherworld/src/main/java/fr/koka99cab/sanctuary26/anotherworld/vault/VaultService.java index 3dbcfd8..2f3b0a1 100644 --- a/anotherworld/src/main/java/fr/koka99cab/sanctuary26/anotherworld/vault/VaultService.java +++ b/anotherworld/src/main/java/fr/koka99cab/sanctuary26/anotherworld/vault/VaultService.java @@ -61,15 +61,18 @@ public final class VaultService { net.minecraft.core.BlockPos center = VaultStructure.centerFor(pos, state); var facing = state.getValue(SafeVaultBlock.FACING); var width = facing.getClockWise(); - for (int column = 0; column < VaultRules.STRUCTURE_WIDTH; column++) { - for (int row = 0; row < VaultRules.STRUCTURE_WIDTH; row++) { - var member = center.relative(width, column - VaultRules.STRUCTURE_RADIUS) - .above(row - VaultRules.STRUCTURE_RADIUS); - BlockState memberState = level.getBlockState(member); - if (!memberState.is(AnotherWorldBlocks.SAFE_BLOCK)) continue; - level.setBlock(member, memberState - .setValue(SafeVaultBlock.FORMED, false) - .setValue(SafeVaultBlock.PART, VaultPart.CENTER), Block.UPDATE_CLIENTS); + for (int depth = 0; depth < VaultRules.STRUCTURE_DEPTH; depth++) { + for (int column = 0; column < VaultRules.STRUCTURE_WIDTH; column++) { + for (int row = 0; row < VaultRules.STRUCTURE_WIDTH; row++) { + var member = center.relative(width, column - VaultRules.STRUCTURE_RADIUS) + .above(row - VaultRules.STRUCTURE_RADIUS) + .relative(facing, depth - VaultRules.STRUCTURE_RADIUS); + BlockState memberState = level.getBlockState(member); + if (!memberState.is(AnotherWorldBlocks.SAFE_BLOCK)) continue; + level.setBlock(member, memberState + .setValue(SafeVaultBlock.FORMED, false) + .setValue(SafeVaultBlock.PART, VaultPart.CENTER), Block.UPDATE_CLIENTS); + } } } } diff --git a/anotherworld/src/main/java/fr/koka99cab/sanctuary26/anotherworld/vault/VaultStructure.java b/anotherworld/src/main/java/fr/koka99cab/sanctuary26/anotherworld/vault/VaultStructure.java index 83507c9..a0d6303 100644 --- a/anotherworld/src/main/java/fr/koka99cab/sanctuary26/anotherworld/vault/VaultStructure.java +++ b/anotherworld/src/main/java/fr/koka99cab/sanctuary26/anotherworld/vault/VaultStructure.java @@ -12,7 +12,7 @@ import net.minecraft.core.Direction; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.Level; -/** Detects one isolated, upright 3x3 vault wall owned by a single player. */ +/** Detects one isolated, hollow 3x3x3 vault cube owned by a single player. */ public final class VaultStructure { private VaultStructure() { } @@ -29,41 +29,66 @@ public final class VaultStructure { offsetWidth <= VaultRules.STRUCTURE_RADIUS; offsetWidth++) { for (int offsetY = -VaultRules.STRUCTURE_RADIUS; offsetY <= VaultRules.STRUCTURE_RADIUS; offsetY++) { - BlockPos center = member.relative(width, -offsetWidth).below(offsetY); - Formed formed = validate(level, center, facing); - if (formed != null && formed.contains(member)) return Optional.of(formed); + for (int offsetDepth = -VaultRules.STRUCTURE_RADIUS; + offsetDepth <= VaultRules.STRUCTURE_RADIUS; offsetDepth++) { + BlockPos center = member.relative(width, -offsetWidth).below(offsetY) + .relative(facing, -offsetDepth); + Formed formed = validate(level, center, facing); + if (formed != null && formed.contains(member)) return Optional.of(formed); + } } } } return Optional.empty(); } + public static Optional findAtCenter(Level level, BlockPos center) { + if (level == null || center == null) return Optional.empty(); + for (Direction direction : Direction.Plane.HORIZONTAL) { + Optional formed = find(level, center.relative(direction)) + .filter(candidate -> candidate.center().equals(center)); + if (formed.isPresent()) return formed; + } + return Optional.empty(); + } + private static Formed validate(Level level, BlockPos center, Direction facing) { UUID owner = null; List members = new ArrayList<>(VaultRules.STRUCTURE_BLOCKS); int partMask = 0; Direction width = facing.getClockWise(); - for (int column = 0; column < VaultRules.STRUCTURE_WIDTH; column++) { - for (int row = 0; row < VaultRules.STRUCTURE_WIDTH; row++) { - BlockPos pos = center.relative(width, column - VaultRules.STRUCTURE_RADIUS) - .above(row - VaultRules.STRUCTURE_RADIUS); - if (!level.getBlockState(pos).is(AnotherWorldBlocks.SAFE_BLOCK) - || !(level.getBlockEntity(pos) instanceof SafeVaultBlockEntity blockEntity) - || blockEntity.owner() == null) return null; - if (owner == null) owner = blockEntity.owner(); - else if (!owner.equals(blockEntity.owner())) return null; - members.add(pos.immutable()); - partMask |= 1 << (row * VaultRules.STRUCTURE_WIDTH + column); + for (int depth = 0; depth < VaultRules.STRUCTURE_DEPTH; depth++) { + for (int column = 0; column < VaultRules.STRUCTURE_WIDTH; column++) { + for (int row = 0; row < VaultRules.STRUCTURE_WIDTH; row++) { + BlockPos pos = center.relative(width, column - VaultRules.STRUCTURE_RADIUS) + .above(row - VaultRules.STRUCTURE_RADIUS) + .relative(facing, depth - VaultRules.STRUCTURE_RADIUS); + if (column == VaultRules.STRUCTURE_RADIUS && row == VaultRules.STRUCTURE_RADIUS + && depth == VaultRules.STRUCTURE_RADIUS) { + if (level.getBlockState(pos).is(AnotherWorldBlocks.SAFE_BLOCK)) return null; + continue; + } + if (!level.getBlockState(pos).is(AnotherWorldBlocks.SAFE_BLOCK) + || !(level.getBlockEntity(pos) instanceof SafeVaultBlockEntity blockEntity) + || blockEntity.owner() == null) return null; + if (owner == null) owner = blockEntity.owner(); + else if (!owner.equals(blockEntity.owner())) return null; + members.add(pos.immutable()); + partMask |= 1 << VaultRules.partIndex(column, row, depth); + } } } if (!VaultRules.isCompleteStructureMask(partMask)) return null; - // Reject ambiguous 3x4/4x3 walls: a vault is one discrete upright 3x3 structure. + // Reject attached Safe Blocks: a vault is one discrete 3x3x3 cube. for (int widthOffset = -2; widthOffset <= 2; widthOffset++) { for (int yOffset = -2; yOffset <= 2; yOffset++) { - if (Math.abs(widthOffset) <= 1 && Math.abs(yOffset) <= 1) continue; - if (level.getBlockState(center.relative(width, widthOffset).above(yOffset)) - .is(AnotherWorldBlocks.SAFE_BLOCK)) return null; + for (int depthOffset = -2; depthOffset <= 2; depthOffset++) { + if (Math.abs(widthOffset) <= 1 && Math.abs(yOffset) <= 1 + && Math.abs(depthOffset) <= 1) continue; + if (level.getBlockState(center.relative(width, widthOffset).above(yOffset) + .relative(facing, depthOffset)).is(AnotherWorldBlocks.SAFE_BLOCK)) return null; + } } } return new Formed(center.immutable(), owner, List.copyOf(members), facing); @@ -73,7 +98,9 @@ public final class VaultStructure { VaultPart part = state.getValue(SafeVaultBlock.PART); Direction width = state.getValue(SafeVaultBlock.FACING).getClockWise(); return member.relative(width, VaultRules.STRUCTURE_RADIUS - part.column()) - .below(part.row() - VaultRules.STRUCTURE_RADIUS).immutable(); + .below(part.row() - VaultRules.STRUCTURE_RADIUS) + .relative(state.getValue(SafeVaultBlock.FACING), VaultRules.STRUCTURE_RADIUS - part.depth()) + .immutable(); } public record Formed(BlockPos center, UUID owner, List members, Direction facing) { @@ -86,7 +113,13 @@ public final class VaultStructure { int column = (pos.getX() - center.getX()) * width.getStepX() + (pos.getZ() - center.getZ()) * width.getStepZ() + VaultRules.STRUCTURE_RADIUS; int row = pos.getY() - center.getY() + VaultRules.STRUCTURE_RADIUS; - return VaultPart.at(column, row); + int depth = (pos.getX() - center.getX()) * facing.getStepX() + + (pos.getZ() - center.getZ()) * facing.getStepZ() + VaultRules.STRUCTURE_RADIUS; + return VaultPart.at(column, row, depth); + } + + public BlockPos controller() { + return center.relative(facing, VaultRules.STRUCTURE_RADIUS).immutable(); } } } diff --git a/anotherworld/src/main/resources/fabric.mod.json b/anotherworld/src/main/resources/fabric.mod.json index 92bad06..93bd7ec 100644 --- a/anotherworld/src/main/resources/fabric.mod.json +++ b/anotherworld/src/main/resources/fabric.mod.json @@ -37,7 +37,7 @@ "sanctuary26": { "system_type": "gameplay-system", "namespace": "anotherworld", - "data_version": 3, + "data_version": 4, "network_protocol": 1, "lifecycle": "${lifecycle}", "pack_version": "${pack_version}", diff --git a/anotherworld/src/test/java/fr/koka99cab/sanctuary26/anotherworld/vault/VaultRulesSmoke.java b/anotherworld/src/test/java/fr/koka99cab/sanctuary26/anotherworld/vault/VaultRulesSmoke.java index fe4cc49..a424bbd 100644 --- a/anotherworld/src/test/java/fr/koka99cab/sanctuary26/anotherworld/vault/VaultRulesSmoke.java +++ b/anotherworld/src/test/java/fr/koka99cab/sanctuary26/anotherworld/vault/VaultRulesSmoke.java @@ -6,18 +6,36 @@ public final class VaultRulesSmoke { public static void main(String[] args) { require(VaultRules.STRUCTURE_WIDTH == 3, "vault width"); - require(VaultRules.STRUCTURE_BLOCKS == 9, "upright vault wall"); - require(!VaultRules.isCompleteStructureMask(0b000_000_111), - "horizontal 3x1 must stay unformed"); - require(!VaultRules.isCompleteStructureMask(0b001_001_001), - "vertical 3x1 must stay unformed"); - require(VaultRules.isCompleteStructureMask(0b111_111_111), - "only all nine parts form the vault"); + require(VaultRules.STRUCTURE_DEPTH == 3, "vault depth"); + require(VaultRules.STRUCTURE_POSITIONS == 27, "vault cube positions"); + require(VaultRules.STRUCTURE_BLOCKS == 26, "hollow vault shell"); + require(VaultRules.CENTER_PART_INDEX == VaultRules.partIndex(1, 1, 1), "free center index"); + require(Integer.bitCount(VaultRules.COMPLETE_STRUCTURE_MASK) == VaultRules.STRUCTURE_BLOCKS, + "26 required shell positions"); + require(!VaultRules.isCompleteStructureMask((1 << 9) - 1), + "3x3 wall must stay unformed"); + require(!VaultRules.isCompleteStructureMask(VaultRules.FULL_STRUCTURE_MASK), + "solid cube must stay unformed"); + require(VaultRules.isCompleteStructureMask(VaultRules.COMPLETE_STRUCTURE_MASK), + "only the complete 26-block shell forms the vault"); require(VaultRules.STORAGE_SLOTS == 27, "vault capacity"); require(VaultRules.CURRENCY_SLOTS == 3, "currency controls"); - require(VaultPart.at(0, 0) == VaultPart.BOTTOM_LEFT, "bottom-left CTM part"); - require(VaultPart.at(1, 1) == VaultPart.CENTER, "center CTM part"); - require(VaultPart.at(2, 2) == VaultPart.TOP_RIGHT, "top-right CTM part"); + require(VaultPart.at(0, 0, 0) == VaultPart.BACK_BOTTOM_LEFT, "back-bottom-left CTM part"); + require(VaultPart.at(1, 1, 1) == VaultPart.CENTER, "center CTM part"); + require(VaultPart.at(2, 2, 2) == VaultPart.FRONT_TOP_RIGHT, "front-top-right CTM part"); + boolean[] seenParts = new boolean[VaultRules.STRUCTURE_POSITIONS]; + for (int depth = 0; depth < VaultRules.STRUCTURE_DEPTH; depth++) { + for (int row = 0; row < VaultRules.STRUCTURE_WIDTH; row++) { + for (int column = 0; column < VaultRules.STRUCTURE_WIDTH; column++) { + int index = VaultRules.partIndex(column, row, depth); + require(!seenParts[index], "unique cube part index"); + seenParts[index] = true; + VaultPart part = VaultPart.at(column, row, depth); + require(part.column() == column && part.row() == row && part.depth() == depth, + "cube part coordinates"); + } + } + } require(VaultRules.buttonCurrency(0) == 0 && VaultRules.buttonCurrency(11) == 2, "button currency routing"); require(VaultRules.buttonAmount(0) == 64 && VaultRules.buttonAmount(1) == 1 diff --git a/build.gradle b/build.gradle index 8da50ed..d747aa5 100644 --- a/build.gradle +++ b/build.gradle @@ -1815,7 +1815,7 @@ tasks.register("verifyWorkspace") { || terramixMigration.compatibility?.new_registry_ids != 100 || terramixMigration.compatibility?.worldgen_changed != true || terramixMigration.compatibility?.save_migration_required != false - || anotherWorldManifest?.custom?.sanctuary26?.data_version != 3 + || anotherWorldManifest?.custom?.sanctuary26?.data_version < 3 || anotherWorldManifest?.custom?.sanctuary26?.network_protocol != 1 || sanctuaryManifest?.custom?.sanctuary26?.data_version < 9 ) { @@ -3478,7 +3478,7 @@ tasks.register("verifyMirrorLavenderDrawerRelease") { "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.204" - || rootProject.anotherworld_version != "0.0.0-alpha.52" + || rootProject.anotherworld_version != "0.0.0-alpha.53" || rootProject.redstoner_version != "0.0.0-alpha.10" || release.pack_version != rootProject.pack_version || index.versionId != rootProject.pack_version || migration.source?.pack_version != "26.2.0-alpha.191" @@ -4585,7 +4585,7 @@ tasks.register("verifyWaystoneShopCulinaryAlpha202Release") { def sanctuaryManifest = new JsonSlurper().parse(file( "sanctuary/src/main/resources/fabric.mod.json")) if (rootProject.pack_version != "26.2.0-alpha.204" - || rootProject.anotherworld_version != "0.0.0-alpha.52" + || rootProject.anotherworld_version != "0.0.0-alpha.53" || rootProject.itsalive_version != "0.0.0-alpha.31" || rootProject.iliketomoveit_version != "0.0.0-alpha.14" || rootProject.sanctuary_version != "0.0.0-alpha.117" @@ -4639,15 +4639,17 @@ tasks.register("verifyWaystoneShopCulinaryAlpha202Release") { tasks.register("verifySafeVaultFeature") { group = "verification" - description = "Checks the current upright 3x3 safe, custom currency interface, drill and Shop contract." + description = "Checks the current hollow 3x3x3 safe, custom currency interface, drill and Shop contract." dependsOn ":anotherworld:generateAnotherWorldResources" inputs.file(file("pack/migrations/anotherworld-safe-vault-data-v2.json")) inputs.file(file("pack/migrations/anotherworld-safe-vault-data-v3.json")) inputs.file(file("pack/migrations/anotherworld-safe-vault-fix-alpha52.json")) + inputs.file(file("pack/migrations/anotherworld-safe-vault-cube-alpha53.json")) inputs.file(file("anotherworld/src/main/resources/fabric.mod.json")) inputs.file(file("sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/api/inventory/SanctuaryCurrencyStorage.java")) inputs.files(fileTree("anotherworld/src/main/java/fr/koka99cab/sanctuary26/anotherworld/vault") { include "*.java" }) inputs.file(file("anotherworld/src/main/java/fr/koka99cab/sanctuary26/anotherworld/block/SafeVaultBlock.java")) + inputs.file(file("anotherworld/src/main/java/fr/koka99cab/sanctuary26/anotherworld/block/entity/SafeVaultBlockEntity.java")) inputs.file(file("anotherworld/src/main/java/fr/koka99cab/sanctuary26/anotherworld/client/SafeVaultScreen.java")) inputs.files(fileTree("anotherworld/src/main/resources/assets/anotherworld/textures/block/safe_vault") { include "*.png" }) inputs.file(file("anotherworld/src/main/resources/assets/anotherworld/textures/block/safe_block.png")) @@ -4657,12 +4659,14 @@ tasks.register("verifySafeVaultFeature") { def migration = new JsonSlurper().parse(file("pack/migrations/anotherworld-safe-vault-data-v2.json")) def interfaceMigration = new JsonSlurper().parse(file("pack/migrations/anotherworld-safe-vault-data-v3.json")) def fixMigration = new JsonSlurper().parse(file("pack/migrations/anotherworld-safe-vault-fix-alpha52.json")) + def cubeMigration = new JsonSlurper().parse(file("pack/migrations/anotherworld-safe-vault-cube-alpha53.json")) def manifest = new JsonSlurper().parse(file("anotherworld/src/main/resources/fabric.mod.json")) def rules = file("anotherworld/src/main/java/fr/koka99cab/sanctuary26/anotherworld/vault/VaultRules.java").text def data = file("anotherworld/src/main/java/fr/koka99cab/sanctuary26/anotherworld/vault/VaultData.java").text def structure = file("anotherworld/src/main/java/fr/koka99cab/sanctuary26/anotherworld/vault/VaultStructure.java").text def menu = file("anotherworld/src/main/java/fr/koka99cab/sanctuary26/anotherworld/vault/SafeVaultMenu.java").text def block = file("anotherworld/src/main/java/fr/koka99cab/sanctuary26/anotherworld/block/SafeVaultBlock.java").text + def controller = file("anotherworld/src/main/java/fr/koka99cab/sanctuary26/anotherworld/block/entity/SafeVaultBlockEntity.java").text def screen = file("anotherworld/src/main/java/fr/koka99cab/sanctuary26/anotherworld/client/SafeVaultScreen.java").text def currencyApi = file("sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/api/inventory/SanctuaryCurrencyStorage.java").text def generated = file("anotherworld/build/generated/resources") @@ -4671,6 +4675,38 @@ tasks.register("verifySafeVaultFeature") { def safeBlockState = new JsonSlurper().parse(new File(generated, "assets/anotherworld/blockstates/safe_block.json")) def safeUnformedModel = new JsonSlurper().parse(new File(generated, "assets/anotherworld/models/block/safe_block_unformed.json")) def safeItemModel = new JsonSlurper().parse(new File(generated, "assets/anotherworld/models/item/safe_block.json")) + def safeFormedModels = fileTree(new File(generated, "assets/anotherworld/models/block/safe_block")) { + include "*.json" + }.files + def safeModelsById = safeFormedModels.collectEntries { modelFile -> + [(modelFile.name.substring(0, modelFile.name.length() - 5)): new JsonSlurper().parse(modelFile)] + } + def normalizedUv = { uv -> + uv.collect { value -> Math.round((value as double) * 1_000_000D) }.join(":") + } + def expectedSurfaceUvs = (0..2).collectMany { column -> + (0..2).collect { row -> + normalizedUv([ + column * 16.0D / 3.0D, (2 - row) * 16.0D / 3.0D, + (column + 1) * 16.0D / 3.0D, (3 - row) * 16.0D / 3.0D + ]) + } + }.toSet() + def basePartName = { id -> id.replaceFirst(/^(back_|front_)/, "") } + def surfaceUvSets = [ + front: safeModelsById.findAll { id, model -> id.startsWith("front_") }.values() + .collect { model -> normalizedUv(model.elements[0].faces.north.uv) }.toSet(), + back: safeModelsById.findAll { id, model -> id.startsWith("back_") }.values() + .collect { model -> normalizedUv(model.elements[0].faces.south.uv) }.toSet(), + west: safeModelsById.findAll { id, model -> basePartName(id).endsWith("_left") }.values() + .collect { model -> normalizedUv(model.elements[0].faces.west.uv) }.toSet(), + east: safeModelsById.findAll { id, model -> basePartName(id).endsWith("_right") }.values() + .collect { model -> normalizedUv(model.elements[0].faces.east.uv) }.toSet(), + top: safeModelsById.findAll { id, model -> basePartName(id).startsWith("top_") }.values() + .collect { model -> normalizedUv(model.elements[0].faces.up.uv) }.toSet(), + bottom: safeModelsById.findAll { id, model -> basePartName(id).startsWith("bottom_") }.values() + .collect { model -> normalizedUv(model.elements[0].faces.down.uv) }.toSet() + ] def requiredTextures = [ "block/safe_vault/bottom.png": [48, 48], "block/safe_vault/front.png": [48, 48], "block/safe_vault/side.png": [48, 48], "block/safe_vault/top.png": [48, 48], @@ -4682,7 +4718,7 @@ tasks.register("verifySafeVaultFeature") { image == null || image.width != size[0] || image.height != size[1] }.keySet() if (rootProject.pack_version != "26.2.0-alpha.204" - || rootProject.anotherworld_version != "0.0.0-alpha.52" + || rootProject.anotherworld_version != "0.0.0-alpha.53" || rootProject.sanctuary_version != "0.0.0-alpha.117" || migration.schema_version != 1 || migration.source?.pack_version != "26.2.0-alpha.201" @@ -4735,25 +4771,56 @@ tasks.register("verifySafeVaultFeature") { || fixMigration.compatibility?.registry_ids_changed != false || fixMigration.compatibility?.saved_currency_balances_preserved != true || fixMigration.compatibility?.pack_release_updated != true + || cubeMigration.source?.pack_version != "26.2.0-alpha.204" + || cubeMigration.source?.modules != [anotherworld: "0.0.0-alpha.52"] + || cubeMigration.target?.pack_version != "26.2.0-alpha.204" + || cubeMigration.target?.modules != [anotherworld: "0.0.0-alpha.53"] + || cubeMigration.safe_vault_structure?.source_footprint != "3x3_upright_wall" + || cubeMigration.safe_vault_structure?.target_footprint != "3x3x3_hollow_cube" + || cubeMigration.safe_vault_structure?.blocks_required != 26 + || cubeMigration.safe_vault_structure?.center_must_be_safe_block != false + || cubeMigration.safe_vault_structure?.hidden_center_block_allowed != true + || cubeMigration.safe_vault_structure?.solid_cube_accepted != false + || cubeMigration.safe_vault_structure?.connected_texture_positions != 26 + || cubeMigration.safe_vault_structure?.surface_texture_grid != "3x3" + || cubeMigration.compatibility?.anotherworld_source_data_version != 3 + || cubeMigration.compatibility?.anotherworld_target_data_version != 4 + || cubeMigration.compatibility?.vault_source_data_version != 2 + || cubeMigration.compatibility?.vault_target_data_version != 3 + || cubeMigration.compatibility?.anotherworld_network_protocol != 1 + || cubeMigration.compatibility?.saved_currency_balances_preserved != true + || cubeMigration.compatibility?.legacy_3x3_wall_requires_rebuild != true + || cubeMigration.compatibility?.registry_ids_changed != false + || cubeMigration.compatibility?.pack_release_updated != false || manifest.depends?.sanctuary != ">=0.0.0-alpha.117" - || manifest.custom?.sanctuary26?.data_version != 3 + || manifest.custom?.sanctuary26?.data_version != 4 || !file("build.gradle").text.contains('implementation project(":sanctuary")') || !currencyApi.contains("public interface Provider") || !currencyApi.contains("countAdditional") || !currencyApi.contains("removeAdditional") - || !rules.contains("STRUCTURE_WIDTH = 3") || !rules.contains("COMPLETE_STRUCTURE_MASK") + || !rules.contains("STRUCTURE_WIDTH = 3") || !rules.contains("STRUCTURE_DEPTH = 3") + || !rules.contains("STRUCTURE_POSITIONS = STRUCTURE_WIDTH * STRUCTURE_WIDTH * STRUCTURE_DEPTH") + || !rules.contains("STRUCTURE_BLOCKS = STRUCTURE_POSITIONS - 1") + || !rules.contains("FULL_STRUCTURE_MASK & ~(1 << CENTER_PART_INDEX)") + || !rules.contains("COMPLETE_STRUCTURE_MASK") || !rules.contains("CURRENCY_SLOTS = 3") || !rules.contains("BUTTONS_PER_CURRENCY = 4") || !rules.contains("RAID_DURATION_TICKS = 60 * 20") || !rules.contains("RAID_LOOT_PERCENT = 20") || !rules.contains("24L * 60L * 60L * 20L") - || !data.contains('SAVE_KEY = "safe_vaults"') || !data.contains("DATA_VERSION = 2") - || !structure.contains("upright 3x3") || !structure.contains(".above(row - VaultRules.STRUCTURE_RADIUS)") + || !data.contains('SAVE_KEY = "safe_vaults"') || !data.contains("DATA_VERSION = 3") + || !structure.contains("hollow 3x3x3") || !structure.contains(".above(row - VaultRules.STRUCTURE_RADIUS)") + || !structure.contains(".relative(facing, depth - VaultRules.STRUCTURE_RADIUS)") + || !structure.contains("findAtCenter") || !structure.contains("controller()") || !structure.contains("isCompleteStructureMask(partMask)") || !block.contains("BooleanProperty FORMED") || !block.contains("EnumProperty PART") + || !block.contains("formed.controller()") + || !controller.contains("VaultStructure.find") || !controller.contains("formed.center()") || !menu.contains("clickMenuButton") || !menu.contains("SanctuaryPlayerInventoryView.unlockedRows") || !menu.contains("PLAYER_INVENTORY_Y = 108") || !screen.contains("handleInventoryButtonClick") || !screen.contains('"− ×64", "− ×1", "+ ×1", "+ ×64"') || screen.contains("inventoryLabelY =") - || safeBlockState.multipart?.size() != 37 - || safeBlockState.multipart?.count { it.when?.formed == "true" } != 36 + || safeBlockState.multipart?.size() != 109 + || safeBlockState.multipart?.count { it.when?.formed == "true" } != 108 + || safeFormedModels.size() != 27 + || surfaceUvSets.any { face, uvs -> uvs != expectedSurfaceUvs } || safeUnformedModel.textures?.all != "anotherworld:block/safe_block" || safeItemModel.parent != "anotherworld:block/safe_block_unformed" || safeRecipe.pattern != ["GGG", "GCG", "GGG"] @@ -4780,7 +4847,7 @@ tasks.register("verifySafeVaultInterfaceAlpha203Release") { def packwizIndex = file("pack/packwiz/index.toml").text def jeiFiles = prism.files.findAll { it.path == "mods/jei-26.2-fabric-30.27.0.188.jar" } if (rootProject.pack_version != "26.2.0-alpha.204" - || rootProject.anotherworld_version != "0.0.0-alpha.52" + || rootProject.anotherworld_version != "0.0.0-alpha.53" || migration.source?.pack_version != "26.2.0-alpha.202" || migration.source?.modules != [anotherworld: "0.0.0-alpha.50"] || migration.target?.pack_version != "26.2.0-alpha.203" @@ -4826,7 +4893,7 @@ tasks.register("verifySafeVaultFixAlpha204Release") { def packwizIndex = file("pack/packwiz/index.toml").text def jeiFiles = prism.files.findAll { it.path == "mods/jei-26.2-fabric-30.27.0.188.jar" } if (rootProject.pack_version != "26.2.0-alpha.204" - || rootProject.anotherworld_version != "0.0.0-alpha.52" + || rootProject.anotherworld_version != "0.0.0-alpha.53" || migration.source?.pack_version != "26.2.0-alpha.203" || migration.source?.modules != [anotherworld: "0.0.0-alpha.51"] || migration.target?.pack_version != "26.2.0-alpha.204" diff --git a/gradle.properties b/gradle.properties index 73795ab..7d8eeff 100644 --- a/gradle.properties +++ b/gradle.properties @@ -16,7 +16,7 @@ ambiance_version=0.0.0-alpha.12 ambiance_lifecycle=active redstoner_version=0.0.0-alpha.10 redstoner_lifecycle=active -anotherworld_version=0.0.0-alpha.52 +anotherworld_version=0.0.0-alpha.53 anotherworld_lifecycle=active itsalive_version=0.0.0-alpha.31 itsalive_lifecycle=active diff --git a/pack/migrations/anotherworld-safe-vault-cube-alpha53.json b/pack/migrations/anotherworld-safe-vault-cube-alpha53.json new file mode 100644 index 0000000..80fc8d8 --- /dev/null +++ b/pack/migrations/anotherworld-safe-vault-cube-alpha53.json @@ -0,0 +1,42 @@ +{ + "schema_version": 1, + "source": { + "pack_version": "26.2.0-alpha.204", + "modules": { + "anotherworld": "0.0.0-alpha.52" + } + }, + "target": { + "pack_version": "26.2.0-alpha.204", + "modules": { + "anotherworld": "0.0.0-alpha.53" + } + }, + "safe_vault_structure": { + "source_footprint": "3x3_upright_wall", + "target_footprint": "3x3x3_hollow_cube", + "blocks_required": 26, + "center_must_be_safe_block": false, + "hidden_center_block_allowed": true, + "solid_cube_accepted": false, + "connected_texture_positions": 26, + "surface_texture_grid": "3x3", + "surface_textures": [ + "anotherworld:block/safe_vault/front", + "anotherworld:block/safe_vault/side", + "anotherworld:block/safe_vault/top", + "anotherworld:block/safe_vault/bottom" + ] + }, + "compatibility": { + "anotherworld_source_data_version": 3, + "anotherworld_target_data_version": 4, + "vault_source_data_version": 2, + "vault_target_data_version": 3, + "anotherworld_network_protocol": 1, + "saved_currency_balances_preserved": true, + "legacy_3x3_wall_requires_rebuild": true, + "registry_ids_changed": false, + "pack_release_updated": false + } +} -- 2.54.0