From b61044abb50371c625d374523e13edefea5d88fd Mon Sep 17 00:00:00 2001 From: Anthony Calosa Date: Sat, 13 Sep 2025 08:57:29 +0800 Subject: [PATCH 1/2] prevent crash for missing card, fix crash Android 8-13 NoSuchMethodError --- .../src/forge/adventure/util/CardUtil.java | 11 ++++++++++- .../main/java/forge/deck/DeckImportController.java | 5 ++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/forge-gui-mobile/src/forge/adventure/util/CardUtil.java b/forge-gui-mobile/src/forge/adventure/util/CardUtil.java index 95735027514..f25b3ba451b 100644 --- a/forge-gui-mobile/src/forge/adventure/util/CardUtil.java +++ b/forge-gui-mobile/src/forge/adventure/util/CardUtil.java @@ -809,6 +809,11 @@ public class CardUtil { return generateBoosterPackAsDeck(edition); } + private static PaperCard getReplacement(String missingCard, String replacementCard) { + System.err.println(missingCard + " : Not found in the database.\nReplacement card: " + replacementCard); + return FModel.getMagicDb().getCommonCards().getCard(replacementCard); + } + public static PaperCard getCardByName(String cardName) { List validCards; //Faster to ask the CardDB for a card name than it is to search the pool. @@ -817,6 +822,10 @@ public class CardUtil { else validCards = FModel.getMagicDb().getCommonCards().getUniqueCardsNoAlt(cardName); + if (validCards.isEmpty()) { + return getReplacement(cardName, "Wastes"); + } + return validCards.get(Current.world().getRandom().nextInt(validCards.size())); } @@ -828,7 +837,7 @@ public class CardUtil { .filter(input -> input.getEdition().equals(edition)).collect(Collectors.toList()); if (validCards.isEmpty()) { - System.err.println("Unexpected behavior: tried to call getCardByNameAndEdition for card " + cardName + " from the edition " + edition + ", but didn't find it in the DB. A random existing instance will be returned."); + System.err.println("Unexpected behavior: tried to call getCardByNameAndEdition for card " + cardName + " from the edition " + edition + ", but didn't find it in the DB. A random existing instance will be returned if found."); return getCardByName(cardName); } diff --git a/forge-gui/src/main/java/forge/deck/DeckImportController.java b/forge-gui/src/main/java/forge/deck/DeckImportController.java index dbf89e11c2f..08065936d82 100644 --- a/forge-gui/src/main/java/forge/deck/DeckImportController.java +++ b/forge-gui/src/main/java/forge/deck/DeckImportController.java @@ -22,6 +22,7 @@ import org.apache.commons.lang3.tuple.Pair; import java.text.DateFormatSymbols; import java.util.*; import java.util.function.Function; +import java.util.stream.Collectors; public class DeckImportController { public enum ImportBehavior { @@ -516,7 +517,9 @@ public class DeckImportController { PaperCard card = token.getCard(); String cardName = card.getName(); CardPool substitutes = availableInventory.getFilteredPool(c -> c.getName().equals(cardName)); - List> sortedSubstitutes = StreamUtil.stream(substitutes).sorted(Comparator.comparingInt(Map.Entry::getValue)).toList(); + // Stream.toList() is only supported on Android 14 and above ref: https://developer.android.com/reference/java/util/stream/Stream#toList() + // use Collectors.toList() to support Android 8 to 13.... + List> sortedSubstitutes = StreamUtil.stream(substitutes).sorted(Comparator.comparingInt(Map.Entry::getValue)).collect(Collectors.toList()); int neededQuantity = token.getQuantity(); for(Token found : replacementList) { //If there's an item in the replacement list already it means we've already found some of the needed copies. From 02b7e408dc67951262957cfb53272cf7574e9c2c Mon Sep 17 00:00:00 2001 From: Anthony Calosa Date: Sat, 13 Sep 2025 09:09:04 +0800 Subject: [PATCH 2/2] fix crash Android 8-13 NoSuchMethodError, prevent crash for missing card --- forge-core/src/main/java/forge/StaticData.java | 2 +- forge-game/src/main/java/forge/game/card/CardState.java | 8 ++++---- .../src/main/java/forge/deck/DeckImportController.java | 4 ++-- .../main/java/forge/gamemodes/limited/BoosterDraft.java | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/forge-core/src/main/java/forge/StaticData.java b/forge-core/src/main/java/forge/StaticData.java index ace76500d2a..249724aa50a 100644 --- a/forge-core/src/main/java/forge/StaticData.java +++ b/forge-core/src/main/java/forge/StaticData.java @@ -878,7 +878,7 @@ public class StaticData { } } } - // stream().toList() causes crash on Android, use Collectors.toList() + // stream().toList() causes crash on Android 8-13, use Collectors.toList() List NIF = new ArrayList<>(NIF_Q).stream().sorted().collect(Collectors.toList()); List CNI = new ArrayList<>(CNI_Q).stream().sorted().collect(Collectors.toList()); List TOK = new ArrayList<>(TOKEN_Q).stream().sorted().collect(Collectors.toList()); diff --git a/forge-game/src/main/java/forge/game/card/CardState.java b/forge-game/src/main/java/forge/game/card/CardState.java index fbe570cfc0a..bdc892d36b0 100644 --- a/forge-game/src/main/java/forge/game/card/CardState.java +++ b/forge-game/src/main/java/forge/game/card/CardState.java @@ -367,7 +367,7 @@ public class CardState extends GameObject implements IHasSVars, ITranslatable { public final FCollectionView getManaAbilities() { FCollection newCol = new FCollection<>(); updateSpellAbilities(newCol, true); - // stream().toList() causes crash on Android, use Collectors.toList() + // stream().toList() causes crash on Android 8-13, use Collectors.toList() newCol.addAll(abilities.stream().filter(SpellAbility::isManaAbility).collect(Collectors.toList())); card.updateSpellAbilities(newCol, this, true); return newCol; @@ -375,7 +375,7 @@ public class CardState extends GameObject implements IHasSVars, ITranslatable { public final FCollectionView getNonManaAbilities() { FCollection newCol = new FCollection<>(); updateSpellAbilities(newCol, false); - // stream().toList() causes crash on Android, use Collectors.toList() + // stream().toList() causes crash on Android 8-13, use Collectors.toList() newCol.addAll(abilities.stream().filter(Predicate.not(SpellAbility::isManaAbility)).collect(Collectors.toList())); card.updateSpellAbilities(newCol, this, false); return newCol; @@ -390,7 +390,7 @@ public class CardState extends GameObject implements IHasSVars, ITranslatable { if (null != mana) { leftAbilities = leftAbilities.stream() .filter(mana ? SpellAbility::isManaAbility : Predicate.not(SpellAbility::isManaAbility)) - // stream().toList() causes crash on Android, use Collectors.toList() + // stream().toList() causes crash on Android 8-13, use Collectors.toList() .collect(Collectors.toList()); } newCol.addAll(leftAbilities); @@ -402,7 +402,7 @@ public class CardState extends GameObject implements IHasSVars, ITranslatable { if (null != mana) { rightAbilities = rightAbilities.stream() .filter(mana ? SpellAbility::isManaAbility : Predicate.not(SpellAbility::isManaAbility)) - // stream().toList() causes crash on Android, use Collectors.toList() + // stream().toList() causes crash on Android 8-13, use Collectors.toList() .collect(Collectors.toList()); } newCol.addAll(rightAbilities); diff --git a/forge-gui/src/main/java/forge/deck/DeckImportController.java b/forge-gui/src/main/java/forge/deck/DeckImportController.java index 08065936d82..0686e65b2ff 100644 --- a/forge-gui/src/main/java/forge/deck/DeckImportController.java +++ b/forge-gui/src/main/java/forge/deck/DeckImportController.java @@ -517,8 +517,8 @@ public class DeckImportController { PaperCard card = token.getCard(); String cardName = card.getName(); CardPool substitutes = availableInventory.getFilteredPool(c -> c.getName().equals(cardName)); - // Stream.toList() is only supported on Android 14 and above ref: https://developer.android.com/reference/java/util/stream/Stream#toList() - // use Collectors.toList() to support Android 8 to 13.... + // stream().toList() causes crash on Android 8-13, use Collectors.toList() + // ref: https://developer.android.com/reference/java/util/stream/Stream#toList() List> sortedSubstitutes = StreamUtil.stream(substitutes).sorted(Comparator.comparingInt(Map.Entry::getValue)).collect(Collectors.toList()); int neededQuantity = token.getQuantity(); for(Token found : replacementList) { diff --git a/forge-gui/src/main/java/forge/gamemodes/limited/BoosterDraft.java b/forge-gui/src/main/java/forge/gamemodes/limited/BoosterDraft.java index 066c986f047..9e8fbfa1cab 100644 --- a/forge-gui/src/main/java/forge/gamemodes/limited/BoosterDraft.java +++ b/forge-gui/src/main/java/forge/gamemodes/limited/BoosterDraft.java @@ -437,7 +437,7 @@ public class BoosterDraft implements IBoosterDraft { CompletableFuture.allOf(futuresArray).join(); futures.clear(); } - // stream().toList() causes crash on Android, use Collectors.toList() + // stream().toList() causes crash on Android 8-13, use Collectors.toList() customs.addAll(queue.stream().collect(Collectors.toList())); } return customs;