diff --git a/CHANGELOG.md b/CHANGELOG.md index cc658ec..fbc8983 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 à soldes illimités + +Module modifié : **Another World `0.0.0-alpha.55`**. Le pack distribué reste en **`26.2.0-alpha.206`** jusqu’à la prochaine release. + +- remplace les 27 piles du coffre-fort par trois soldes 64 bits séparés pour les rubis, saphirs et émeraudes ; +- migre automatiquement chaque pile existante vers son solde correspondant dans `safe_vaults` v4, sans changer les IDs ni exiger de nouveau monde ; +- synchronise les soldes 64 bits avec l’interface et les affiche sous forme compacte au-delà de 999 ; +- conserve l’accès du Shop aux monnaies stockées et borne son API historique 32 bits sans limiter le solde réel ; +- crédite directement les 20 % pillés dans le coffre-fort de l’attaquant afin d’éviter de générer un nombre non borné d’objets ; +- porte Another World en donnée v5 et protocole réseau 2 ; Packwiz sera actualisé lors de la prochaine version distribuée. + ## `26.2.0-alpha.206` Module modifié : **Another World `0.0.0-alpha.54`**. Les neuf autres modules conservent leur version issue de l’alpha.205. diff --git a/README.md b/README.md index 95ba7d3..cb12c44 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,8 @@ Les descriptions FR/EN/RU sont déclarées dans chaque `fabric.mod.json` et dans Version courante du pack distribué : **`26.2.0-alpha.206`**. Cette release distribue Another World **`0.0.0-alpha.54`** avec les colonnes A et C corrigées sur la façade et le dos du coffre-fort. +Dans le workspace en développement, Another World **`0.0.0-alpha.55`** rend le stockage du coffre-fort pratiquement illimité : rubis, saphirs et émeraudes utilisent chacun un solde 64 bits. Les 27 anciennes piles sont additionnées automatiquement lors du chargement, le Shop continue de consommer directement ces soldes et les 20 % pillés sont crédités dans le coffre-fort de l’attaquant sans créer des millions d’entités ou de piles d’objets. + L’alpha.206 exige une coque creuse de **3 × 3 × 3**, soit 26 Safe Blocks appartenant au même joueur, et conserve JEI en **`30.27.0.189`**. 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. Les colonnes extrêmes de la façade et du dos sont désormais orientées correctement. 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. diff --git a/anotherworld/build.gradle b/anotherworld/build.gradle index c72a62a..2b9d65b 100644 --- a/anotherworld/build.gradle +++ b/anotherworld/build.gradle @@ -1640,7 +1640,7 @@ tasks.register("generateAnotherWorldResources") { "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 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.deposit_failed": "Impossible de déposer %s : aucun exemplaire disponible dans votre inventaire.", "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.", "message.anotherworld.safe_vault.break_not_owner": "Seul le propriétaire peut démonter ce bloc de coffre-fort.", @@ -1656,7 +1656,7 @@ tasks.register("generateAnotherWorldResources") { "message.anotherworld.safe_vault.drilling_progress": "Forage : %s %%", "message.anotherworld.safe_vault.drilling_cancelled": "Forage interrompu.", "message.anotherworld.safe_vault.raided_owner": "%s a pillé %s gemme(s) dans votre coffre-fort.", - "message.anotherworld.safe_vault.raided": "Pillage réussi : %s gemme(s) récupérée(s)." + "message.anotherworld.safe_vault.raided": "Pillage réussi : %s gemme(s) créditée(s) dans votre coffre-fort." ] : locale == "ru_ru" ? [ "container.anotherworld.safe_vault": "Сейф", "tooltip.anotherworld.titanium_drill.use": "Удерживайте на чужом сейфе, чтобы просверлить его.", @@ -1664,7 +1664,7 @@ tasks.register("generateAnotherWorldResources") { "message.anotherworld.safe_vault.incomplete": "Сейф требует полую оболочку 3 × 3 × 3 из 26 блоков одного владельца со свободным центром.", "message.anotherworld.safe_vault.locked": "Этот сейф принадлежит другому игроку.", "message.anotherworld.safe_vault.formed": "Полый сейф 3 × 3 × 3 привязан к вам в точке %s, %s, %s.", - "message.anotherworld.safe_vault.deposit_failed": "Не удалось внести %s: предметов нет или сейф заполнен.", + "message.anotherworld.safe_vault.deposit_failed": "Не удалось внести %s: в инвентаре нет доступных предметов.", "message.anotherworld.safe_vault.withdraw_failed": "Не удалось забрать %s: баланс пуст или инвентарь заполнен.", "message.anotherworld.safe_vault.already_owned": "У вас уже есть активный сейф.", "message.anotherworld.safe_vault.break_not_owner": "Разобрать этот блок может только владелец.", @@ -1680,7 +1680,7 @@ tasks.register("generateAnotherWorldResources") { "message.anotherworld.safe_vault.drilling_progress": "Сверление: %s %%", "message.anotherworld.safe_vault.drilling_cancelled": "Сверление прервано.", "message.anotherworld.safe_vault.raided_owner": "%s похитил(а) %s драгоценностей из вашего сейфа.", - "message.anotherworld.safe_vault.raided": "Ограбление удалось: получено %s драгоценностей." + "message.anotherworld.safe_vault.raided": "Ограбление удалось: %s драгоценностей зачислено в ваш сейф." ] : [ "container.anotherworld.safe_vault": "Safe", "tooltip.anotherworld.titanium_drill.use": "Hold on an enemy safe to drill it.", @@ -1688,7 +1688,7 @@ tasks.register("generateAnotherWorldResources") { "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": "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.deposit_failed": "Could not deposit %s: none are available in your inventory.", "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.", "message.anotherworld.safe_vault.break_not_owner": "Only the owner may dismantle this safe block.", @@ -1704,7 +1704,7 @@ tasks.register("generateAnotherWorldResources") { "message.anotherworld.safe_vault.drilling_progress": "Drilling: %s %%", "message.anotherworld.safe_vault.drilling_cancelled": "Drilling interrupted.", "message.anotherworld.safe_vault.raided_owner": "%s stole %s gem item(s) from your safe.", - "message.anotherworld.safe_vault.raided": "Raid successful: recovered %s gem item(s)." + "message.anotherworld.safe_vault.raided": "Raid successful: %s gem item(s) credited to your safe." ] lang.putAll(vaultText) def tagTranslations = locale == "fr_fr" ? [ @@ -1953,7 +1953,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.54" + if (project.version.toString() != "0.0.0-alpha.55" || sulfurMigration.target?.modules?.anotherworld != "0.0.0-alpha.44" || sulfurMigration.sulfur_caves?.sanctuary?.dripstone_cell_period != 2 || !javaText.contains('Identifier.parse("sanctuary:sulfur_caves")') @@ -3270,7 +3270,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.54" + if (project.version.toString() != "0.0.0-alpha.55" || 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/client/SafeVaultScreen.java b/anotherworld/src/main/java/fr/koka99cab/sanctuary26/anotherworld/client/SafeVaultScreen.java index 71e4db3..c114b55 100644 --- a/anotherworld/src/main/java/fr/koka99cab/sanctuary26/anotherworld/client/SafeVaultScreen.java +++ b/anotherworld/src/main/java/fr/koka99cab/sanctuary26/anotherworld/client/SafeVaultScreen.java @@ -53,7 +53,7 @@ public final class SafeVaultScreen extends AbstractContainerScreen= 0 && currency < VaultRules.CURRENCY_SLOTS ? balances.get(currency) : 0; + public long balance(int currency) { + if (currency < 0 || currency >= VaultRules.CURRENCY_SLOTS) return 0L; + int base = currency * VaultRules.BALANCE_WORDS_PER_CURRENCY; + return VaultRules.balanceFromWords(balances.get(base), balances.get(base + 1)); } public static Item currency(int currency) { @@ -110,16 +112,16 @@ public final class SafeVaultMenu extends AbstractContainerMenu { private int deposit(ServerPlayer player, Item item, int requested) { int available = countPlayerItem(player, item); - int amount = Math.min(requested, Math.min(available, vaultData.capacity(owner, item))); + int amount = Math.min(requested, available); if (amount <= 0) return 0; int removed = removePlayerItem(player, item, amount); - int stored = vaultData.add(owner, item, removed); + int stored = (int) vaultData.add(owner, item, removed); if (stored < removed) insertPlayerItem(player, item, removed - stored); return stored; } private int withdraw(ServerPlayer player, Item item, int requested) { - int amount = Math.min(requested, + int amount = (int) Math.min(requested, Math.min(vaultData.count(owner, item), playerCapacity(player, item))); if (amount <= 0) return 0; int removed = vaultData.remove(owner, item, amount); @@ -184,12 +186,15 @@ public final class SafeVaultMenu extends AbstractContainerMenu { private record VaultBalanceData(VaultData data, UUID owner) implements ContainerData { @Override public int get(int index) { - return index >= 0 && index < CURRENCIES.size() ? data.count(owner, CURRENCIES.get(index)) : 0; + if (index < 0 || index >= VaultRules.BALANCE_DATA_SLOTS) return 0; + int currency = index / VaultRules.BALANCE_WORDS_PER_CURRENCY; + int word = index % VaultRules.BALANCE_WORDS_PER_CURRENCY; + return VaultRules.balanceWord(data.count(owner, CURRENCIES.get(currency)), word); } @Override public void set(int index, int value) { } @Override public int getCount() { - return VaultRules.CURRENCY_SLOTS; + return VaultRules.BALANCE_DATA_SLOTS; } } } 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 ee47baf..3303d7d 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 @@ -9,28 +9,27 @@ import java.util.List; import java.util.Map; import java.util.UUID; import net.minecraft.core.BlockPos; -import net.minecraft.core.NonNullList; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.resources.Identifier; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.Container; -import net.minecraft.world.ContainerHelper; -import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.Level; import net.minecraft.world.level.saveddata.SavedData; import net.minecraft.world.level.saveddata.SavedDataType; -/** Global player-linked vault inventory, available to the Shop even when its structure is unloaded. */ +/** Global player-linked unlimited currency balances, available even when the vault is unloaded. */ public final class VaultData extends SavedData { public static final String SAVE_KEY = "safe_vaults"; - 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"); + public static final int DATA_VERSION = 4; + private static final List CURRENCIES = List.of( + Identifier.fromNamespaceAndPath("anotherworld", "ruby"), + Identifier.fromNamespaceAndPath("anotherworld", "sapphire"), + Identifier.withDefaultNamespace("emerald") + ); + /** Legacy v3 slot format, retained only so existing worlds migrate monotonically. */ private static final Codec SLOT_CODEC = RecordCodecBuilder.create(instance -> instance.group( Codec.INT.fieldOf("slot").forGetter(StoredSlot::slot), ItemStack.CODEC.fieldOf("item").forGetter(StoredSlot::item) @@ -38,6 +37,9 @@ public final class VaultData extends SavedData { private static final Codec ACCOUNT_CODEC = RecordCodecBuilder.create(instance -> instance.group( Codec.STRING.fieldOf("owner").forGetter(StoredAccount::owner), SLOT_CODEC.listOf().optionalFieldOf("items", List.of()).forGetter(StoredAccount::items), + Codec.LONG.optionalFieldOf("rubies", 0L).forGetter(StoredAccount::rubies), + Codec.LONG.optionalFieldOf("sapphires", 0L).forGetter(StoredAccount::sapphires), + Codec.LONG.optionalFieldOf("emeralds", 0L).forGetter(StoredAccount::emeralds), Codec.LONG.optionalFieldOf("protected_until", 0L).forGetter(StoredAccount::protectedUntil) ).apply(instance, StoredAccount::new)); private static final Codec ADDRESS_CODEC = RecordCodecBuilder.create(instance -> instance.group( @@ -66,13 +68,13 @@ public final class VaultData extends SavedData { for (StoredAccount stored : accounts == null ? List.of() : accounts) { try { UUID owner = UUID.fromString(stored.owner()); - NonNullList items = NonNullList.withSize(VaultRules.STORAGE_SLOTS, ItemStack.EMPTY); + Account account = new Account(stored.rubies(), stored.sapphires(), stored.emeralds(), + stored.protectedUntil()); for (StoredSlot slot : stored.items()) { - if (slot.slot() >= 0 && slot.slot() < items.size() && isCurrency(slot.item())) { - items.set(slot.slot(), slot.item().copy()); - } + int currency = currencyIndex(slot.item()); + if (currency >= 0) account.add(currency, slot.item().getCount()); } - this.accounts.put(owner, new Account(items, Math.max(0L, stored.protectedUntil()))); + this.accounts.put(owner, account); } catch (IllegalArgumentException ignored) { AnotherWorldMod.LOGGER.warn("[Vault] Ignored account with invalid owner {}.", stored.owner()); } @@ -123,86 +125,59 @@ public final class VaultData extends SavedData { if (changed) setDirty(); } - public Container container(UUID owner, ServerLevel level, BlockPos center) { - accounts.computeIfAbsent(owner, ignored -> new Account()); - return new VaultContainer(this, owner, level, center.immutable()); + public long count(UUID owner, Item item) { + int currency = currencyIndex(item); + Account account = accounts.get(owner); + return currency < 0 || account == null ? 0L : account.balance(currency); } - public int count(UUID owner, Item item) { - if (!isCurrency(item)) return 0; - Account account = accounts.get(owner); - if (account == null) return 0; - long count = 0L; - for (ItemStack stack : account.items) if (stack.is(item)) count += stack.getCount(); - return (int) Math.min(Integer.MAX_VALUE, count); + public int countCapped(UUID owner, Item item) { + return (int) Math.min(Integer.MAX_VALUE, count(owner, item)); } public int remove(UUID owner, Item item, int amount) { - if (!isCurrency(item) || amount <= 0) return 0; + return (int) remove(owner, item, (long) amount); + } + + public long remove(UUID owner, Item item, long amount) { + int currency = currencyIndex(item); Account account = accounts.get(owner); - if (account == null) return 0; - int remaining = amount; - for (ItemStack stack : account.items) { - if (remaining <= 0) break; - if (!stack.is(item)) continue; - int removed = Math.min(remaining, stack.getCount()); - stack.shrink(removed); - remaining -= removed; - } - if (remaining != amount) setDirty(); - return amount - remaining; + if (currency < 0 || account == null || amount <= 0L) return 0L; + long removed = account.remove(currency, amount); + if (removed > 0L) setDirty(); + return removed; } - public int capacity(UUID owner, Item item) { - if (!isCurrency(item)) return 0; - long capacity = 0L; - for (ItemStack stack : items(owner)) { - if (stack.isEmpty()) capacity += item.getDefaultMaxStackSize(); - else if (stack.is(item)) capacity += Math.max(0, stack.getMaxStackSize() - stack.getCount()); - } - return (int) Math.min(Integer.MAX_VALUE, capacity); - } - - public int add(UUID owner, Item item, int amount) { - if (!isCurrency(item) || amount <= 0) return 0; - NonNullList stored = items(owner); - int remaining = amount; - for (ItemStack stack : stored) { - if (remaining <= 0) break; - if (!stack.is(item) || stack.getCount() >= stack.getMaxStackSize()) continue; - int inserted = Math.min(remaining, stack.getMaxStackSize() - stack.getCount()); - stack.grow(inserted); - remaining -= inserted; - } - for (int slot = 0; slot < stored.size() && remaining > 0; slot++) { - if (!stored.get(slot).isEmpty()) continue; - int inserted = Math.min(remaining, item.getDefaultMaxStackSize()); - stored.set(slot, new ItemStack(item, inserted)); - remaining -= inserted; - } - if (remaining != amount) setDirty(); - return amount - remaining; + public long add(UUID owner, Item item, long amount) { + int currency = currencyIndex(item); + if (currency < 0 || amount <= 0L) return 0L; + long added = accounts.computeIfAbsent(owner, ignored -> new Account()).add(currency, amount); + if (added > 0L) setDirty(); + return added; } public boolean hasContents(UUID owner) { Account account = accounts.get(owner); - return account != null && account.items.stream().anyMatch(stack -> !stack.isEmpty()); + return account != null && account.hasContents(); } - public List raidLoot(UUID owner) { - List loot = new ArrayList<>(); - for (Identifier id : List.of(RUBY, SAPPHIRE, EMERALD)) { - Item item = BuiltInRegistries.ITEM.getOptional(id).orElse(null); - if (item == null) continue; - int amount = VaultRules.raidLoot(count(owner, item)); - int removed = remove(owner, item, amount); - while (removed > 0) { - int stackSize = Math.min(removed, item.getDefaultMaxStackSize()); - loot.add(new ItemStack(item, stackSize)); - removed -= stackSize; - } + /** Transfers the exact 20% raid share into the attacker's unlimited balances without item spam. */ + public long transferRaid(UUID owner, UUID attacker) { + if (owner == null || attacker == null || owner.equals(attacker)) return 0L; + Account source = accounts.get(owner); + if (source == null) return 0L; + Account target = accounts.computeIfAbsent(attacker, ignored -> new Account()); + long total = 0L; + for (int currency = 0; currency < VaultRules.CURRENCY_SLOTS; currency++) { + long requested = VaultRules.raidLoot(source.balance(currency)); + long accepted = Math.min(requested, VaultRules.MAX_BALANCE - target.balance(currency)); + long removed = source.remove(currency, accepted); + long added = target.add(currency, removed); + if (added < removed) source.add(currency, removed - added); + total = VaultRules.saturatingAdd(total, added); } - return List.copyOf(loot); + if (total > 0L) setDirty(); + return total; } public long protectionRemaining(UUID owner, long gameTime) { @@ -217,90 +192,74 @@ public final class VaultData extends SavedData { } public static boolean isCurrency(ItemStack stack) { - return stack != null && !stack.isEmpty() && isCurrency(stack.getItem()); + return stack != null && !stack.isEmpty() && currencyIndex(stack) >= 0; } public static boolean isCurrency(Item item) { - if (item == null) return false; - Identifier id = BuiltInRegistries.ITEM.getKey(item); - return RUBY.equals(id) || SAPPHIRE.equals(id) || EMERALD.equals(id); - } - - private NonNullList items(UUID owner) { - return accounts.computeIfAbsent(owner, ignored -> new Account()).items; + return currencyIndex(item) >= 0; } private List storedAccounts() { List stored = new ArrayList<>(); - accounts.forEach((owner, account) -> { - List slots = new ArrayList<>(); - for (int index = 0; index < account.items.size(); index++) { - ItemStack stack = account.items.get(index); - if (!stack.isEmpty()) slots.add(new StoredSlot(index, stack)); - } - stored.add(new StoredAccount(owner.toString(), List.copyOf(slots), account.protectedUntil)); - }); + accounts.forEach((owner, account) -> stored.add(new StoredAccount(owner.toString(), List.of(), + account.balance(0), account.balance(1), account.balance(2), account.protectedUntil))); return List.copyOf(stored); } + private static int currencyIndex(ItemStack stack) { + return stack == null || stack.isEmpty() ? -1 : currencyIndex(stack.getItem()); + } + + private static int currencyIndex(Item item) { + if (item == null) return -1; + return CURRENCIES.indexOf(BuiltInRegistries.ITEM.getKey(item)); + } + private static String dimension(ServerLevel level) { return level.dimension().identifier().toString(); } private static final class Account { - private final NonNullList items; + private final long[] balances = new long[VaultRules.CURRENCY_SLOTS]; private long protectedUntil; - private Account() { this(NonNullList.withSize(VaultRules.STORAGE_SLOTS, ItemStack.EMPTY), 0L); } - private Account(NonNullList items, long protectedUntil) { - this.items = items; - this.protectedUntil = protectedUntil; - } - } - private static final class VaultContainer implements Container { - private final VaultData data; - private final UUID owner; - private final ServerLevel level; - private final BlockPos center; - - private VaultContainer(VaultData data, UUID owner, ServerLevel level, BlockPos center) { - this.data = data; - this.owner = owner; - this.level = level; - this.center = center; + private Account() { + this(0L, 0L, 0L, 0L); } - @Override public int getContainerSize() { return VaultRules.STORAGE_SLOTS; } - @Override public boolean isEmpty() { return data.items(owner).stream().allMatch(ItemStack::isEmpty); } - @Override public ItemStack getItem(int slot) { return data.items(owner).get(slot); } - @Override public ItemStack removeItem(int slot, int amount) { - ItemStack removed = ContainerHelper.removeItem(data.items(owner), slot, amount); - if (!removed.isEmpty()) setChanged(); + private Account(long rubies, long sapphires, long emeralds, long protectedUntil) { + balances[0] = Math.max(0L, rubies); + balances[1] = Math.max(0L, sapphires); + balances[2] = Math.max(0L, emeralds); + this.protectedUntil = Math.max(0L, protectedUntil); + } + + private long balance(int currency) { + return balances[currency]; + } + + private long add(int currency, long amount) { + if (amount <= 0L) return 0L; + long previous = balances[currency]; + balances[currency] = VaultRules.saturatingAdd(previous, amount); + return balances[currency] - previous; + } + + private long remove(int currency, long amount) { + if (amount <= 0L) return 0L; + long removed = Math.min(amount, balances[currency]); + balances[currency] -= removed; return removed; } - @Override public ItemStack removeItemNoUpdate(int slot) { - ItemStack removed = ContainerHelper.takeItem(data.items(owner), slot); - if (!removed.isEmpty()) setChanged(); - return removed; + + private boolean hasContents() { + for (long balance : balances) if (balance > 0L) return true; + return false; } - @Override public void setItem(int slot, ItemStack stack) { - if (stack != null && !stack.isEmpty() && !isCurrency(stack)) return; - data.items(owner).set(slot, stack == null ? ItemStack.EMPTY : stack); - setChanged(); - } - @Override public void setChanged() { data.setDirty(); } - @Override public boolean stillValid(Player player) { - if (player == null || !player.getUUID().equals(owner) - || 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.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); } } private record StoredSlot(int slot, ItemStack item) {} - private record StoredAccount(String owner, List items, long protectedUntil) {} + private record StoredAccount(String owner, List items, long rubies, long sapphires, + long emeralds, long protectedUntil) {} private record VaultAddress(String dimension, long pos, String owner) {} } diff --git a/anotherworld/src/main/java/fr/koka99cab/sanctuary26/anotherworld/vault/VaultDrillingService.java b/anotherworld/src/main/java/fr/koka99cab/sanctuary26/anotherworld/vault/VaultDrillingService.java index ca89d9d..74d71b8 100644 --- a/anotherworld/src/main/java/fr/koka99cab/sanctuary26/anotherworld/vault/VaultDrillingService.java +++ b/anotherworld/src/main/java/fr/koka99cab/sanctuary26/anotherworld/vault/VaultDrillingService.java @@ -3,7 +3,6 @@ package fr.koka99cab.sanctuary26.anotherworld.vault; import fr.koka99cab.sanctuary26.anotherworld.AnotherWorldMod; import fr.koka99cab.sanctuary26.anotherworld.registry.AnotherWorldItems; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.UUID; import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; @@ -125,14 +124,12 @@ public final class VaultDrillingService { private static void complete(ServerPlayer attacker, ServerLevel level, Session session, VaultStructure.Formed formed) { VaultData data = VaultData.get(level.getServer()); - List loot = data.raidLoot(session.owner()); - if (loot.isEmpty()) { + long total = data.transferRaid(session.owner(), attacker.getUUID()); + if (total <= 0L) { cancel(attacker, true); attacker.stopUsingItem(); return; } - int total = loot.stream().mapToInt(ItemStack::getCount).sum(); - loot.forEach(stack -> attacker.getInventory().placeItemBackInInventory(stack)); data.protectAfterRaid(session.owner(), level.getServer().overworld().getGameTime()); ItemStack drill = attacker.getItemInHand(session.hand()); if (!attacker.hasInfiniteMaterials()) drill.shrink(1); 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 0b9ea79..d6f502a 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 @@ -11,9 +11,11 @@ public final class VaultRules { + 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 BALANCE_WORDS_PER_CURRENCY = 2; + public static final int BALANCE_DATA_SLOTS = CURRENCY_SLOTS * BALANCE_WORDS_PER_CURRENCY; public static final int BUTTONS_PER_CURRENCY = 4; + public static final long MAX_BALANCE = Long.MAX_VALUE; public static final int RAID_DURATION_TICKS = 60 * 20; public static final int RAID_LOOT_PERCENT = 20; public static final long RAID_PROTECTION_TICKS = 24L * 60L * 60L * 20L; @@ -21,10 +23,47 @@ public final class VaultRules { private VaultRules() { } - public static int raidLoot(int stored) { + public static long raidLoot(long stored) { if (stored <= 0) return 0; - long rounded = ((long) stored * RAID_LOOT_PERCENT + 99L) / 100L; - return (int) Math.min(stored, Math.max(1L, rounded)); + long rounded = stored / 100L * RAID_LOOT_PERCENT + + (stored % 100L * RAID_LOOT_PERCENT + 99L) / 100L; + return Math.min(stored, Math.max(1L, rounded)); + } + + public static long saturatingAdd(long current, long added) { + if (current < 0L || added <= 0L) return Math.max(0L, current); + return added > MAX_BALANCE - current ? MAX_BALANCE : current + added; + } + + public static int balanceWord(long balance, int word) { + long safe = Math.clamp(balance, 0L, MAX_BALANCE); + return switch (word) { + case 0 -> (int) safe; + case 1 -> (int) (safe >>> 32); + default -> 0; + }; + } + + public static long balanceFromWords(int low, int high) { + return Math.max(0L, ((long) high << 32) | Integer.toUnsignedLong(low)); + } + + public static String formatBalance(long balance) { + long safe = Math.max(0L, balance); + if (safe < 1_000L) return Long.toString(safe); + long[] units = {1_000L, 1_000_000L, 1_000_000_000L, 1_000_000_000_000L, + 1_000_000_000_000_000L}; + String[] suffixes = {"K", "M", "B", "T", "Q"}; + int unitIndex = units.length - 1; + while (unitIndex > 0 && safe < units[unitIndex]) unitIndex--; + long unit = units[unitIndex]; + long whole = safe / unit; + if (whole >= 1_000L) return safe / 1_000_000_000_000_000_000L + "E18"; + if (whole < 10L) { + long tenth = safe % unit * 10L / unit; + if (tenth > 0L) return whole + "." + tenth + suffixes[unitIndex]; + } + return whole + suffixes[unitIndex]; } public static int progressPercent(int elapsedTicks) { 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 2f3b0a1..6488d35 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 @@ -26,7 +26,7 @@ public final class VaultService { initialized = true; SanctuaryCurrencyStorage.register(AnotherWorldMod.id("safe_vault"), new SanctuaryCurrencyStorage.Provider() { @Override public int count(ServerPlayer player, net.minecraft.world.item.Item item) { - return VaultData.get(player.level().getServer()).count(player.getUUID(), item); + return VaultData.get(player.level().getServer()).countCapped(player.getUUID(), item); } @Override public int remove(ServerPlayer player, net.minecraft.world.item.Item item, int amount) { return VaultData.get(player.level().getServer()).remove(player.getUUID(), item, amount); diff --git a/anotherworld/src/main/resources/fabric.mod.json b/anotherworld/src/main/resources/fabric.mod.json index 93bd7ec..5a2aae1 100644 --- a/anotherworld/src/main/resources/fabric.mod.json +++ b/anotherworld/src/main/resources/fabric.mod.json @@ -37,8 +37,8 @@ "sanctuary26": { "system_type": "gameplay-system", "namespace": "anotherworld", - "data_version": 4, - "network_protocol": 1, + "data_version": 5, + "network_protocol": 2, "lifecycle": "${lifecycle}", "pack_version": "${pack_version}", "descriptions": { 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 a424bbd..34a8c0f 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 @@ -18,8 +18,9 @@ public final class VaultRulesSmoke { "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(VaultRules.BALANCE_DATA_SLOTS == 6, "64-bit balance synchronization"); + require(VaultRules.MAX_BALANCE == Long.MAX_VALUE, "unlimited practical balance"); 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"); @@ -51,7 +52,18 @@ public final class VaultRulesSmoke { require(VaultRules.raidLoot(1) == 1, "minimum loot"); require(VaultRules.raidLoot(5) == 1, "exact 20 percent"); require(VaultRules.raidLoot(64) == 13, "rounded 20 percent"); - require(VaultRules.raidLoot(Integer.MAX_VALUE) == 429_496_730, "bounded large balance"); + require(VaultRules.raidLoot(Integer.MAX_VALUE) == 429_496_730L, "32-bit balance raid"); + require(VaultRules.raidLoot(Long.MAX_VALUE) == 1_844_674_407_370_955_162L, + "64-bit balance raid without overflow"); + require(VaultRules.saturatingAdd(Long.MAX_VALUE - 2L, 10L) == Long.MAX_VALUE, + "balance saturation without overflow"); + for (long balance : new long[] {0L, Integer.MAX_VALUE, (long) Integer.MAX_VALUE + 1L, Long.MAX_VALUE}) { + require(VaultRules.balanceFromWords(VaultRules.balanceWord(balance, 0), + VaultRules.balanceWord(balance, 1)) == balance, "64-bit balance synchronization round trip"); + } + require(VaultRules.formatBalance(999L).equals("999"), "small balance display"); + require(VaultRules.formatBalance(1_500L).equals("1.5K"), "compact balance display"); + require(VaultRules.formatBalance(1_000_000L).equals("1M"), "million balance display"); require(VaultRules.progressPercent(0) == 0, "initial progress"); require(VaultRules.progressPercent(600) == 50, "half progress"); require(VaultRules.progressPercent(1_200) == 100, "complete progress"); diff --git a/build.gradle b/build.gradle index 3392c98..8b56d8d 100644 --- a/build.gradle +++ b/build.gradle @@ -1816,7 +1816,7 @@ tasks.register("verifyWorkspace") { || terramixMigration.compatibility?.worldgen_changed != true || terramixMigration.compatibility?.save_migration_required != false || anotherWorldManifest?.custom?.sanctuary26?.data_version < 3 - || anotherWorldManifest?.custom?.sanctuary26?.network_protocol != 1 + || anotherWorldManifest?.custom?.sanctuary26?.network_protocol < 1 || sanctuaryManifest?.custom?.sanctuary26?.data_version < 9 ) { throw new GradleException("The alpha.145 native Terramix contract is incomplete") @@ -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.206" - || rootProject.anotherworld_version != "0.0.0-alpha.54" + || rootProject.anotherworld_version != "0.0.0-alpha.55" || 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.206" - || rootProject.anotherworld_version != "0.0.0-alpha.54" + || rootProject.anotherworld_version != "0.0.0-alpha.55" || 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,13 +4639,14 @@ tasks.register("verifyWaystoneShopCulinaryAlpha202Release") { tasks.register("verifySafeVaultFeature") { group = "verification" - description = "Checks the current hollow 3x3x3 safe, custom currency interface, drill and Shop contract." + description = "Checks the current hollow 3x3x3 safe, unlimited balances, 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("pack/migrations/anotherworld-safe-vault-ctm-columns-alpha54.json")) + inputs.file(file("pack/migrations/anotherworld-safe-vault-unlimited-balances-alpha55.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" }) @@ -4662,11 +4663,14 @@ tasks.register("verifySafeVaultFeature") { 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 ctmMigration = new JsonSlurper().parse(file("pack/migrations/anotherworld-safe-vault-ctm-columns-alpha54.json")) + def unlimitedMigration = new JsonSlurper().parse(file( + "pack/migrations/anotherworld-safe-vault-unlimited-balances-alpha55.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 drilling = file("anotherworld/src/main/java/fr/koka99cab/sanctuary26/anotherworld/vault/VaultDrillingService.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 @@ -4741,7 +4745,7 @@ tasks.register("verifySafeVaultFeature") { image == null || image.width != size[0] || image.height != size[1] }.keySet() if (rootProject.pack_version != "26.2.0-alpha.206" - || rootProject.anotherworld_version != "0.0.0-alpha.54" + || rootProject.anotherworld_version != "0.0.0-alpha.55" || rootProject.sanctuary_version != "0.0.0-alpha.117" || migration.schema_version != 1 || migration.source?.pack_version != "26.2.0-alpha.201" @@ -4835,8 +4839,36 @@ tasks.register("verifySafeVaultFeature") { || ctmMigration.compatibility?.saved_currency_balances_preserved != true || ctmMigration.compatibility?.pack_release_updated != true || ctmMigration.distribution?.packwiz_index_refreshed != true + || unlimitedMigration.source?.pack_version != "26.2.0-alpha.206" + || unlimitedMigration.source?.modules != [anotherworld: "0.0.0-alpha.54"] + || unlimitedMigration.target?.pack_version != "26.2.0-alpha.206" + || unlimitedMigration.target?.modules != [anotherworld: "0.0.0-alpha.55"] + || unlimitedMigration.safe_vault_balances?.source_storage != "27_item_stacks" + || unlimitedMigration.safe_vault_balances?.target_storage != "three_64_bit_balances" + || unlimitedMigration.safe_vault_balances?.currencies + != ["anotherworld:ruby", "anotherworld:sapphire", "minecraft:emerald"] + || unlimitedMigration.safe_vault_balances?.maximum_balance_per_currency?.toString() + != "9223372036854775807" + || unlimitedMigration.safe_vault_balances?.legacy_stacks_summed_into_balances != true + || unlimitedMigration.safe_vault_balances?.shop_access_when_structure_unloaded != true + || unlimitedMigration.safe_vault_balances?.client_sync_words_per_currency != 2 + || unlimitedMigration.raid?.loot_percent != 20 + || unlimitedMigration.raid?.loot_credited_to_attacker_safe != true + || unlimitedMigration.raid?.physical_item_spam_avoided != true + || unlimitedMigration.compatibility?.anotherworld_source_data_version != 4 + || unlimitedMigration.compatibility?.anotherworld_target_data_version != 5 + || unlimitedMigration.compatibility?.vault_source_data_version != 3 + || unlimitedMigration.compatibility?.vault_target_data_version != 4 + || unlimitedMigration.compatibility?.anotherworld_source_network_protocol != 1 + || unlimitedMigration.compatibility?.anotherworld_target_network_protocol != 2 + || unlimitedMigration.compatibility?.network_protocol_changed != true + || unlimitedMigration.compatibility?.saved_currency_balances_preserved != true + || unlimitedMigration.compatibility?.registry_ids_changed != false + || unlimitedMigration.compatibility?.pack_release_updated != false + || unlimitedMigration.distribution?.packwiz_index_refreshed != false || manifest.depends?.sanctuary != ">=0.0.0-alpha.117" - || manifest.custom?.sanctuary26?.data_version != 4 + || manifest.custom?.sanctuary26?.data_version != 5 + || manifest.custom?.sanctuary26?.network_protocol != 2 || !file("build.gradle").text.contains('implementation project(":sanctuary")') || !currencyApi.contains("public interface Provider") || !currencyApi.contains("countAdditional") || !currencyApi.contains("removeAdditional") @@ -4846,9 +4878,18 @@ tasks.register("verifySafeVaultFeature") { || !rules.contains("FULL_STRUCTURE_MASK & ~(1 << CENTER_PART_INDEX)") || !rules.contains("COMPLETE_STRUCTURE_MASK") || !rules.contains("CURRENCY_SLOTS = 3") + || !rules.contains("BALANCE_DATA_SLOTS = CURRENCY_SLOTS * BALANCE_WORDS_PER_CURRENCY") + || !rules.contains("MAX_BALANCE = Long.MAX_VALUE") + || !rules.contains("raidLoot(long stored)") + || !rules.contains("balanceFromWords") || !rules.contains("formatBalance") || !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 = 3") + || !data.contains('SAVE_KEY = "safe_vaults"') || !data.contains("DATA_VERSION = 4") + || !data.contains('Codec.LONG.optionalFieldOf("rubies", 0L)') + || !data.contains('Codec.LONG.optionalFieldOf("sapphires", 0L)') + || !data.contains('Codec.LONG.optionalFieldOf("emeralds", 0L)') + || !data.contains("for (StoredSlot slot : stored.items())") + || !data.contains("transferRaid(UUID owner, UUID attacker)") || !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()") @@ -4858,7 +4899,11 @@ tasks.register("verifySafeVaultFeature") { || !controller.contains("VaultStructure.find") || !controller.contains("formed.center()") || !menu.contains("clickMenuButton") || !menu.contains("SanctuaryPlayerInventoryView.unlockedRows") || !menu.contains("PLAYER_INVENTORY_Y = 108") + || !menu.contains("new SimpleContainerData(VaultRules.BALANCE_DATA_SLOTS)") + || !menu.contains("VaultRules.balanceFromWords") + || !drilling.contains("data.transferRaid(session.owner(), attacker.getUUID())") || !screen.contains("handleInventoryButtonClick") || !screen.contains('"− ×64", "− ×1", "+ ×1", "+ ×64"') + || !screen.contains("VaultRules.formatBalance(menu.balance(currency))") || screen.contains("inventoryLabelY =") || safeBlockState.multipart?.size() != 109 || safeBlockState.multipart?.count { it.when?.formed == "true" } != 108 @@ -4892,7 +4937,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.189.jar" } if (rootProject.pack_version != "26.2.0-alpha.206" - || rootProject.anotherworld_version != "0.0.0-alpha.54" + || rootProject.anotherworld_version != "0.0.0-alpha.55" || 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" @@ -4938,7 +4983,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.189.jar" } if (rootProject.pack_version != "26.2.0-alpha.206" - || rootProject.anotherworld_version != "0.0.0-alpha.54" + || rootProject.anotherworld_version != "0.0.0-alpha.55" || 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" @@ -4992,7 +5037,7 @@ tasks.register("verifySafeVaultCubeAlpha205Release") { def packwizIndex = file("pack/packwiz/index.toml").text def jeiFiles = prism.files.findAll { it.path == "mods/jei-26.2-fabric-30.27.0.189.jar" } if (rootProject.pack_version != "26.2.0-alpha.206" - || rootProject.anotherworld_version != "0.0.0-alpha.54" + || rootProject.anotherworld_version != "0.0.0-alpha.55" || migration.source?.pack_version != "26.2.0-alpha.204" || migration.source?.modules != [anotherworld: "0.0.0-alpha.52"] || migration.target?.pack_version != "26.2.0-alpha.205" @@ -5050,7 +5095,7 @@ tasks.register("verifySafeVaultCtmAlpha206Release") { def packwizIndex = file("pack/packwiz/index.toml").text def jeiFiles = prism.files.findAll { it.path == "mods/jei-26.2-fabric-30.27.0.189.jar" } if (rootProject.pack_version != "26.2.0-alpha.206" - || rootProject.anotherworld_version != "0.0.0-alpha.54" + || rootProject.anotherworld_version != "0.0.0-alpha.55" || migration.source?.pack_version != "26.2.0-alpha.205" || migration.source?.modules != [anotherworld: "0.0.0-alpha.53"] || migration.target?.pack_version != "26.2.0-alpha.206" diff --git a/gradle.properties b/gradle.properties index c9ac966..0ba1ab7 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.54 +anotherworld_version=0.0.0-alpha.55 anotherworld_lifecycle=active itsalive_version=0.0.0-alpha.31 itsalive_lifecycle=active diff --git a/pack/migrations/anotherworld-safe-vault-unlimited-balances-alpha55.json b/pack/migrations/anotherworld-safe-vault-unlimited-balances-alpha55.json new file mode 100644 index 0000000..166d36f --- /dev/null +++ b/pack/migrations/anotherworld-safe-vault-unlimited-balances-alpha55.json @@ -0,0 +1,48 @@ +{ + "schema_version": 1, + "source": { + "pack_version": "26.2.0-alpha.206", + "modules": { + "anotherworld": "0.0.0-alpha.54" + } + }, + "target": { + "pack_version": "26.2.0-alpha.206", + "modules": { + "anotherworld": "0.0.0-alpha.55" + } + }, + "safe_vault_balances": { + "source_storage": "27_item_stacks", + "target_storage": "three_64_bit_balances", + "currencies": [ + "anotherworld:ruby", + "anotherworld:sapphire", + "minecraft:emerald" + ], + "maximum_balance_per_currency": 9223372036854775807, + "legacy_stacks_summed_into_balances": true, + "shop_access_when_structure_unloaded": true, + "client_sync_words_per_currency": 2 + }, + "raid": { + "loot_percent": 20, + "loot_credited_to_attacker_safe": true, + "physical_item_spam_avoided": true + }, + "compatibility": { + "anotherworld_source_data_version": 4, + "anotherworld_target_data_version": 5, + "vault_source_data_version": 3, + "vault_target_data_version": 4, + "anotherworld_source_network_protocol": 1, + "anotherworld_target_network_protocol": 2, + "network_protocol_changed": true, + "saved_currency_balances_preserved": true, + "registry_ids_changed": false, + "pack_release_updated": false + }, + "distribution": { + "packwiz_index_refreshed": false + } +}