feat(anotherworld): rendre le coffre-fort illimité #30

Merged
koka merged 1 commits from feature/safe-vault-unlimited-balances into main 2026-08-28 03:50:12 +00:00
14 changed files with 301 additions and 183 deletions
+11
View File
@@ -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 linterface et les affiche sous forme compacte au-delà de 999 ;
- conserve laccè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 lattaquant afin d’éviter de générer un nombre non borné dobjets ;
- 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 lalpha.205.
+2
View File
@@ -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 lattaquant sans créer des millions dentités ou de piles dobjets.
Lalpha.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 sactive plus et doit être démonté puis reconstruit ; les comptes et soldes existants restent conservés.
Lalpha.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. Linterface monétaire affiche désormais correctement l’émeraude et place linventaire étendu sous les trois lignes de monnaie.
+8 -8
View File
@@ -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"
@@ -53,7 +53,7 @@ public final class SafeVaultScreen extends AbstractContainerScreen<SafeVaultMenu
graphics.outline(x, y, 18, 18, 0xFF373737);
graphics.fill(x + 1, y + 1, x + 17, y + 17, 0xFF8B8B8B);
graphics.item(new ItemStack(SafeVaultMenu.currency(currency)), x + 1, y + 1);
Component balance = Component.literal(Integer.toString(menu.balance(currency)));
Component balance = Component.literal(VaultRules.formatBalance(menu.balance(currency)));
graphics.text(font, balance, x + 17 - font.width(balance), y + 9, 0xFFFFFFFF, true);
}
}
@@ -34,7 +34,7 @@ public final class SafeVaultMenu extends AbstractContainerMenu {
private final BlockPos center;
public SafeVaultMenu(int containerId, Inventory inventory) {
this(containerId, inventory, new SimpleContainerData(VaultRules.CURRENCY_SLOTS),
this(containerId, inventory, new SimpleContainerData(VaultRules.BALANCE_DATA_SLOTS),
null, null, null, null);
}
@@ -47,7 +47,7 @@ public final class SafeVaultMenu extends AbstractContainerMenu {
private SafeVaultMenu(int containerId, Inventory inventory, ContainerData balances,
VaultData vaultData, ServerLevel serverLevel, BlockPos center, UUID owner) {
super(AnotherWorldMenus.SAFE_VAULT, containerId);
checkContainerDataCount(balances, VaultRules.CURRENCY_SLOTS);
checkContainerDataCount(balances, VaultRules.BALANCE_DATA_SLOTS);
this.balances = balances;
this.vaultData = vaultData;
this.serverLevel = serverLevel;
@@ -58,8 +58,10 @@ public final class SafeVaultMenu extends AbstractContainerMenu {
addDataSlots(balances);
}
public int balance(int currency) {
return currency >= 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;
}
}
}
@@ -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<Identifier> 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<StoredSlot> 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<StoredAccount> 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<VaultAddress> ADDRESS_CODEC = RecordCodecBuilder.create(instance -> instance.group(
@@ -66,13 +68,13 @@ public final class VaultData extends SavedData {
for (StoredAccount stored : accounts == null ? List.<StoredAccount>of() : accounts) {
try {
UUID owner = UUID.fromString(stored.owner());
NonNullList<ItemStack> 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<ItemStack> 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<ItemStack> raidLoot(UUID owner) {
List<ItemStack> 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<ItemStack> items(UUID owner) {
return accounts.computeIfAbsent(owner, ignored -> new Account()).items;
return currencyIndex(item) >= 0;
}
private List<StoredAccount> storedAccounts() {
List<StoredAccount> stored = new ArrayList<>();
accounts.forEach((owner, account) -> {
List<StoredSlot> 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<ItemStack> 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<ItemStack> 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<StoredSlot> items, long protectedUntil) {}
private record StoredAccount(String owner, List<StoredSlot> items, long rubies, long sapphires,
long emeralds, long protectedUntil) {}
private record VaultAddress(String dimension, long pos, String owner) {}
}
@@ -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<ItemStack> 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);
@@ -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) {
@@ -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);
@@ -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": {
@@ -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");
+56 -11
View File
@@ -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"
+1 -1
View File
@@ -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
@@ -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
}
}