From 0ddf5b25d775954c65facff3da342197156a5a27 Mon Sep 17 00:00:00 2001 From: Anthony Calosa Date: Sun, 3 Nov 2024 10:07:14 +0800 Subject: [PATCH] refactor VCardDisplayArea, prevent NPE --- .../screens/match/views/VCardDisplayArea.java | 132 ++++++++++++------ forge-gui/res/languages/de-DE.properties | 10 +- forge-gui/res/languages/en-US.properties | 10 +- forge-gui/res/languages/es-ES.properties | 10 +- forge-gui/res/languages/fr-FR.properties | 10 +- forge-gui/res/languages/it-IT.properties | 10 +- forge-gui/res/languages/ja-JP.properties | 10 +- forge-gui/res/languages/pt-BR.properties | 10 +- forge-gui/res/languages/zh-CN.properties | 10 +- .../gamemodes/match/input/InputAttack.java | 8 +- .../gamemodes/match/input/InputBlock.java | 6 +- .../gamemodes/match/input/InputPayMana.java | 2 +- 12 files changed, 169 insertions(+), 59 deletions(-) diff --git a/forge-gui-mobile/src/forge/screens/match/views/VCardDisplayArea.java b/forge-gui-mobile/src/forge/screens/match/views/VCardDisplayArea.java index 59a77b709f4..0df3913311b 100644 --- a/forge-gui-mobile/src/forge/screens/match/views/VCardDisplayArea.java +++ b/forge-gui-mobile/src/forge/screens/match/views/VCardDisplayArea.java @@ -23,25 +23,52 @@ import forge.screens.match.MatchController; import forge.toolbox.FCardPanel; import forge.toolbox.FDisplayObject; import forge.util.ThreadUtil; +import io.sentry.Sentry; public abstract class VCardDisplayArea extends VDisplayArea implements ActivateHandler { private static final float CARD_STACK_OFFSET = 0.2f; - protected final List orderedCards = new ArrayList<>(); - protected final List cardPanels = new ArrayList<>(); + protected List _orderedCards; + protected List orderedCards() { + List result = _orderedCards; + if (result == null) { + synchronized (this) { + result = _orderedCards; + if (result == null) { + result = new ArrayList<>(); + _orderedCards = result; + } + } + } + return _orderedCards; + } + protected List _cardPanels; + protected List cardPanels() { + List result = _cardPanels; + if (result == null) { + synchronized (this) { + result = _cardPanels; + if (result == null) { + result = new ArrayList<>(); + _cardPanels = result; + } + } + } + return _cardPanels; + } private boolean rotateCards180; public Iterable getOrderedCards() { - return orderedCards; + return orderedCards(); } public Iterable getCardPanels() { - return cardPanels; + return cardPanels(); } @Override public int getCount() { - return cardPanels.size(); + return cardPanels().size(); } @Override @@ -58,8 +85,8 @@ public abstract class VCardDisplayArea extends VDisplayArea implements ActivateH for (CardView card : model) { CardAreaPanel cardPanel = CardAreaPanel.get(card); addCardPanelToDisplayArea(cardPanel); - cardPanels.add(cardPanel); - if (newCardPanel == null && !orderedCards.contains(card)) { + cardPanels().add(cardPanel); + if (newCardPanel == null && !orderedCards().contains(card)) { newCardPanel = cardPanel; } } @@ -78,7 +105,7 @@ public abstract class VCardDisplayArea extends VDisplayArea implements ActivateH if (isVisible() == b0) { return; } super.setVisible(b0); if (b0) { //when zone becomes visible, ensure display area of panels is updated and panels layed out - for (CardAreaPanel pnl : cardPanels) { + for (CardAreaPanel pnl : cardPanels()) { pnl.displayArea = this; } revalidate(); @@ -113,7 +140,7 @@ public abstract class VCardDisplayArea extends VDisplayArea implements ActivateH CardPanelContainer.this.remove(CardPanel.getDragAnimationPanel()); CardPanelContainer.this.setMouseDragPanel(null); }*/ - cardPanels.remove(fromPanel); + cardPanels().remove(fromPanel); remove(fromPanel); } @@ -124,14 +151,14 @@ public abstract class VCardDisplayArea extends VDisplayArea implements ActivateH @Override public void clear() { super.clear(); - if (!cardPanels.isEmpty()) { - for (CardAreaPanel panel : cardPanels) { + if (!cardPanels().isEmpty()) { + for (CardAreaPanel panel : cardPanels()) { if (panel.displayArea == null || panel.displayArea == this || - !panel.displayArea.cardPanels.contains(panel)) { //don't reset if panel's displayed in another area already + !panel.displayArea.cardPanels().contains(panel)) { //don't reset if panel's displayed in another area already panel.reset(); } } - cardPanels.clear(); + cardPanels().clear(); } } @@ -140,13 +167,16 @@ public abstract class VCardDisplayArea extends VDisplayArea implements ActivateH List attachedPanels = cardPanel.getAttachedPanels(); if (!attachedPanels.isEmpty()) { for (int i = attachedPanels.size() - 1; i >= 0; i--) { - int count = addCards(attachedPanels.get(i), x, y, cardWidth, cardHeight); - x += count * cardWidth * CARD_STACK_OFFSET; - totalCount += count; + CardAreaPanel attachedPanel = attachedPanels.get(i); + if (attachedPanel != null) { + int count = addCards(attachedPanel, x, y, cardWidth, cardHeight); + x += count * cardWidth * CARD_STACK_OFFSET; + totalCount += count; + } } } - orderedCards.add(cardPanel.getCard()); + orderedCards().add(cardPanel.getCard()); cardPanel.setBounds(x, y, cardWidth, cardHeight); if (cardPanel.getNextPanelInStack() != null) { //add next panel in stack if needed @@ -165,16 +195,18 @@ public abstract class VCardDisplayArea extends VDisplayArea implements ActivateH @Override protected ScrollBounds layoutAndGetScrollBounds(float visibleWidth, float visibleHeight) { - orderedCards.clear(); + orderedCards().clear(); float x = 0; float y = 0; float cardHeight = visibleHeight; float cardWidth = getCardWidth(cardHeight); - for (CardAreaPanel cardPanel : cardPanels) { - int count = addCards(cardPanel, x, y, cardWidth, cardHeight); - x += cardWidth + (count - 1) * cardWidth * CARD_STACK_OFFSET; + for (CardAreaPanel cardPanel : new ArrayList<>(cardPanels())) { + if (cardPanel != null) { + int count = addCards(cardPanel, x, y, cardWidth, cardHeight); + x += cardWidth + (count - 1) * cardWidth * CARD_STACK_OFFSET; + } } return new ScrollBounds(x, visibleHeight); @@ -189,30 +221,33 @@ public abstract class VCardDisplayArea extends VDisplayArea implements ActivateH @Override public String getActivateAction(int index) { - if(!GuiBase.isNetworkplay()) //causes lag on netplay client side - return MatchController.instance.getGameController().getActivateDescription(orderedCards.get(index)); + if(!GuiBase.isNetworkplay()) { + //causes lag on netplay client side, also index shouldn't be out of bounds + if (index >= 0 && index < orderedCards().size()) + return MatchController.instance.getGameController().getActivateDescription(orderedCards().get(index)); + } - return "Activate | Cast | Play (if allowed)"; //simple text on card zoom swipe up + return Forge.getLocalizer().getMessage("lblActivateAction"); //simple text on card zoom swipe up } @Override public void setSelectedIndex(int index) { //just scroll card into view - if (index < orderedCards.size()) { - final CardAreaPanel cardPanel = CardAreaPanel.get(orderedCards.get(index)); + if (index < orderedCards().size()) { + final CardAreaPanel cardPanel = CardAreaPanel.get(orderedCards().get(index)); scrollIntoView(cardPanel); } } @Override public void activate(int index) { - final CardAreaPanel cardPanel = CardAreaPanel.get(orderedCards.get(index)); + final CardAreaPanel cardPanel = CardAreaPanel.get(orderedCards().get(index)); //must invoke in game thread in case a dialog needs to be shown ThreadUtil.invokeInGameThread(() -> cardPanel.selectCard(false)); } public static class CardAreaPanel extends FCardPanel { - private static final Map allCardPanels = new HashMap<>(); + private static Map allCardPanels = new HashMap<>(); public static CardAreaPanel get(CardView card0) { CardAreaPanel cardPanel = allCardPanels.get(card0.getId()); @@ -224,19 +259,23 @@ public abstract class VCardDisplayArea extends VDisplayArea implements ActivateH } public static void resetForNewGame() { - for (CardAreaPanel cardPanel : allCardPanels.values()) { - cardPanel.displayArea = null; - cardPanel.attachedToPanel = null; - cardPanel.attachedPanels.clear(); - cardPanel.prevPanelInStack = null; - cardPanel.nextPanelInStack = null; + if (allCardPanels != null) { + for (CardAreaPanel cardPanel : allCardPanels.values()) { + cardPanel.displayArea = null; + cardPanel.attachedToPanel = null; + cardPanel.attachedPanels.clear(); + cardPanel.prevPanelInStack = null; + cardPanel.nextPanelInStack = null; + } + allCardPanels.clear(); + } else { + allCardPanels = new HashMap<>(); } - allCardPanels.clear(); } private VCardDisplayArea displayArea; private CardAreaPanel attachedToPanel; - private final List attachedPanels = new ArrayList<>(); + private List attachedPanels = new ArrayList<>(); private CardAreaPanel nextPanelInStack, prevPanelInStack; //use static get(card) function instead @@ -255,6 +294,12 @@ public abstract class VCardDisplayArea extends VDisplayArea implements ActivateH attachedToPanel = attachedToPanel0; } public List getAttachedPanels() { + if (attachedPanels == null) { + attachedPanels = new ArrayList<>(); + String error = getCard() + " - Attached panel is null."; + Sentry.captureMessage(error); + System.err.println(error); + } return attachedPanels; } public CardAreaPanel getNextPanelInStack() { @@ -295,12 +340,12 @@ public abstract class VCardDisplayArea extends VDisplayArea implements ActivateH } } } - - if (card.getAttachedTo() != null) { - if (card != card.getAttachedTo().getAttachedTo()) - setAttachedToPanel(CardAreaPanel.get(card.getAttachedTo())); + CardView getAttachedto = card.getAttachedTo(); + if (getAttachedto != null) { + if (card != getAttachedto.getAttachedTo()) + setAttachedToPanel(CardAreaPanel.get(getAttachedto)); else { - attachedPanels.remove(CardAreaPanel.get(card.getAttachedTo())); + attachedPanels.remove(CardAreaPanel.get(getAttachedto)); setAttachedToPanel(null); } } @@ -334,7 +379,8 @@ public abstract class VCardDisplayArea extends VDisplayArea implements ActivateH FThreads.invokeInEdtLater(CardAreaPanel.this::showZoom); } else if (!selectCard(false)) { //if no cards in stack can be selected, just show zoom/details for card - FThreads.invokeInEdtLater(CardAreaPanel.this::showZoom); + if (!MatchController.instance.isSelecting()) + FThreads.invokeInEdtLater(CardAreaPanel.this::showZoom); } }); return true; @@ -403,7 +449,7 @@ public abstract class VCardDisplayArea extends VDisplayArea implements ActivateH public void showZoom() { if (displayArea == null) { return; } - final List cards = displayArea.orderedCards; + final List cards = displayArea.orderedCards(); CardZoom.show(cards, cards.indexOf(getCard()), displayArea); } diff --git a/forge-gui/res/languages/de-DE.properties b/forge-gui/res/languages/de-DE.properties index 22181d139bf..5b8c2ac2ddf 100644 --- a/forge-gui/res/languages/de-DE.properties +++ b/forge-gui/res/languages/de-DE.properties @@ -3458,4 +3458,12 @@ lblChooseOrderCardsPutIntoAttractionDeck=Wählen Sie die Reihenfolge der Karten lblAttractionRollResult={0} gewürfelt, um ihre Attraktionen zu besuchen. Ergebnis: {1}. lblAttractionDeckZone=Attraktionsdeck lblLights=Lichter -lblJunkyardZone=schrottplatz \ No newline at end of file +lblJunkyardZone=schrottplatz +lblActivateAction=Aktivieren | Besetzung | Spielen (falls erlaubt) +lblActivateBand=Band mit Karte aktivieren +lblRemoveFromCombat=Karte aus dem Kampf entfernen +lblDeclareAttackersForCard=Angreifer für Karte deklarieren +lblAttackWithCard=Angriff mit Karte +lblDeclareBlockersForCard=Deklarieren Sie Blocker für die Karte +lblBlockWithCard=Mit Karte sperren +lblPayManaWithCard=Bezahle Mana mit Karte \ No newline at end of file diff --git a/forge-gui/res/languages/en-US.properties b/forge-gui/res/languages/en-US.properties index b813947ed45..67e98beab42 100644 --- a/forge-gui/res/languages/en-US.properties +++ b/forge-gui/res/languages/en-US.properties @@ -3177,4 +3177,12 @@ lblRelease=Release lblSnapshot=Snapshot lblNewSnapshotVersion=NEW FORGE-{0}! cbSnapshotUpdate=Check snapshot updates on startup. -nlSnapshotUpdate=When enabled, automatically check snapshot updates on startup and displays the notification on the title bar. \ No newline at end of file +nlSnapshotUpdate=When enabled, automatically check snapshot updates on startup and displays the notification on the title bar. +lblActivateAction=Activate | Cast | Play (if allowed) +lblActivateBand=Activate band with card +lblRemoveFromCombat=Remove card from combat +lblDeclareAttackersForCard=Declare attackers for card +lblAttackWithCard=Attack with card +lblDeclareBlockersForCard=Declare blockers for card +lblBlockWithCard=Block with card +lblPayManaWithCard=Pay mana with card \ No newline at end of file diff --git a/forge-gui/res/languages/es-ES.properties b/forge-gui/res/languages/es-ES.properties index 578fb95302b..6859c9df1f6 100644 --- a/forge-gui/res/languages/es-ES.properties +++ b/forge-gui/res/languages/es-ES.properties @@ -3465,4 +3465,12 @@ lbltoattractiondeck=a la plataforma de atracción lblFrom=de lblChooseOrderCardsPutIntoAttractionDeck=Elige el orden de las cartas para colocarlas en el mazo de atracciones. lblAttractionRollResult={0} rodó para visitar sus atracciones. Resultado: {1}. -lblLights=Luces \ No newline at end of file +lblLights=Luces +lblActivateAction=Activar | Elenco | Jugar (si está permitido) +lblActivateBand=Activar banda con tarjeta +lblRemoveFromCombat=Quitar carta del combate +lblDeclareAttackersForCard=Declarar atacantes para tarjeta. +lblAttackWithCard=Ataque con tarjeta +lblDeclareBlockersForCard=Declarar bloqueadores para tarjeta +lblBlockWithCard=Bloquear con tarjeta +lblPayManaWithCard=Pagar mana con tarjeta \ No newline at end of file diff --git a/forge-gui/res/languages/fr-FR.properties b/forge-gui/res/languages/fr-FR.properties index 5a8d5f569a4..4e3550b8ed7 100644 --- a/forge-gui/res/languages/fr-FR.properties +++ b/forge-gui/res/languages/fr-FR.properties @@ -3466,4 +3466,12 @@ lblChooseOrderCardsPutIntoAttractionDeck=Choisissez l'ordre des cartes à mettre lblAttractionRollResult={0} ont lancé un jet pour visiter leurs attractions. Résultat: {1}. lblAttractionDeckZone=pont d'attraction lblLights=Lumières -lblJunkyardZone=dépotoir \ No newline at end of file +lblJunkyardZone=dépotoir +lblActivateAction=Activer | Casting | Jouer (si autorisé) +lblActivateBand=Activer le groupe avec la carte +lblRemoveFromCombat=Retirer une carte du combat +lblDeclareAttackersForCard=Déclarer les attaquants pour la carte +lblAttackWithCard=Attaque avec une carte +lblDeclareBlockersForCard=Déclarer les bloqueurs de carte +lblBlockWithCard=Bloquer avec une carte +lblPayManaWithCard=Payer du mana avec une carte \ No newline at end of file diff --git a/forge-gui/res/languages/it-IT.properties b/forge-gui/res/languages/it-IT.properties index fb9b42f371a..b7757b2bbf5 100644 --- a/forge-gui/res/languages/it-IT.properties +++ b/forge-gui/res/languages/it-IT.properties @@ -3464,4 +3464,12 @@ lblChooseOrderCardsPutIntoAttractionDeck=Scegli l'ordine delle carte da inserire lblAttractionRollResult={0} ha rotolato per visitare le sue attrazioni. Risultato: {1}. lblAttractionDeckZone=attrazione lblLights=Luci -lblJunkyardZone=discarica \ No newline at end of file +lblJunkyardZone=discarica +lblActivateAction=Attiva | Cast | Gioca (se consentito) +lblActivateBand=Attiva banda con card +lblRemoveFromCombat=Rimuovi la carta dal combattimento +lblDeclareAttackersForCard=Dichiara gli attaccanti per la carta +lblAttackWithCard=Attacca con la carta +lblDeclareBlockersForCard=Dichiarare i bloccanti per la carta +lblBlockWithCard=Blocca con la carta +lblPayManaWithCard=Paga mana con la carta \ No newline at end of file diff --git a/forge-gui/res/languages/ja-JP.properties b/forge-gui/res/languages/ja-JP.properties index cf82b2955da..6bbfe41bcf6 100644 --- a/forge-gui/res/languages/ja-JP.properties +++ b/forge-gui/res/languages/ja-JP.properties @@ -3460,4 +3460,12 @@ lblChooseOrderCardsPutIntoAttractionDeck=アトラクションデッキに入れ lblAttractionRollResult={0} はアトラクションを訪れるために転がりました。結果: {1}。 lblAttractionDeckZone=アトラクションデッキ lblLights=ライト -lblJunkyardZone=廃品置き場 \ No newline at end of file +lblJunkyardZone=廃品置き場 +lblActivateAction=アクティブにする | キャスト | 遊ぶ(許可されている場合) +lblActivateBand=カードでバンドをアクティブにする +lblRemoveFromCombat=カードを戦闘から取り除く +lblDeclareAttackersForCard=カードの攻撃者を宣言する +lblAttackWithCard=カードで攻撃する +lblDeclareBlockersForCard=カードのブロッカーを宣言する +lblBlockWithCard=カードでブロックする +lblPayManaWithCard=カードでマナを支払う \ No newline at end of file diff --git a/forge-gui/res/languages/pt-BR.properties b/forge-gui/res/languages/pt-BR.properties index 1fd254423b5..5eda231c821 100644 --- a/forge-gui/res/languages/pt-BR.properties +++ b/forge-gui/res/languages/pt-BR.properties @@ -3550,4 +3550,12 @@ lblChooseOrderCardsPutIntoAttractionDeck=Escolha a ordem das cartas para colocar lblAttractionRollResult={0} rolou para visitar suas atrações. Resultado: {1}. lblAttractionDeckZone=deck de atração lblLights=Luzes -lblJunkyardZone=ferro-velho \ No newline at end of file +lblJunkyardZone=ferro-velho +lblActivateAction=Ativar | Elenco | Jogue (se permitido) +lblActivateBand=Ativar banda com cartão +lblRemoveFromCombat=Remover carta do combate +lblDeclareAttackersForCard=Declarar atacantes para cartão +lblAttackWithCard=Ataque com cartão +lblDeclareBlockersForCard=Declarar bloqueadores para cartão +lblBlockWithCard=Bloquear com cartão +lblPayManaWithCard=Pague mana com cartão \ No newline at end of file diff --git a/forge-gui/res/languages/zh-CN.properties b/forge-gui/res/languages/zh-CN.properties index 8de5193f6a3..61a03fdc01c 100644 --- a/forge-gui/res/languages/zh-CN.properties +++ b/forge-gui/res/languages/zh-CN.properties @@ -3451,4 +3451,12 @@ lblChooseOrderCardsPutIntoAttractionDeck=选择放入景点牌组的卡片顺序 lblAttractionRollResult={0} 滚动访问他们的景点。结果:{1}。 lblAttractionDeckZone=吸引力甲板 lblLights=灯 -lblJunkyardZone=垃圾场 \ No newline at end of file +lblJunkyardZone=垃圾场 +lblActivateAction=激活 | 演员 | 播放(如果允许) +lblActivateBand=用卡激活手环 +lblRemoveFromCombat=将卡牌从战斗中移除 +lblDeclareAttackersForCard=宣布卡的攻击者 +lblAttackWithCard=用卡攻击 +lblDeclareBlockersForCard=声明卡的拦截器 +lblBlockWithCard=用卡挡住 +lblPayManaWithCard=用卡支付法力 \ No newline at end of file diff --git a/forge-gui/src/main/java/forge/gamemodes/match/input/InputAttack.java b/forge-gui/src/main/java/forge/gamemodes/match/input/InputAttack.java index 70595e51f77..818f44b1506 100644 --- a/forge-gui/src/main/java/forge/gamemodes/match/input/InputAttack.java +++ b/forge-gui/src/main/java/forge/gamemodes/match/input/InputAttack.java @@ -247,18 +247,18 @@ public class InputAttack extends InputSyncronizedBase { public String getActivateAction(Card card) { if (combat.isAttacking(card, currentDefender)) { if (potentialBanding) { - return "activate band with card"; + return Localizer.getInstance().getMessage("lblActivateBand"); } - return "remove card from combat"; + return Localizer.getInstance().getMessage("lblRemoveFromCombat"); } if (card.getController().isOpponentOf(playerAttacks)) { if (defenders.contains(card)) { - return "declare attackers for card"; + return Localizer.getInstance().getMessage("lblDeclareAttackersForCard"); } return null; } if (playerAttacks.getZone(ZoneType.Battlefield).contains(card) && CombatUtil.canAttack(card, currentDefender)) { - return "attack with card"; + return Localizer.getInstance().getMessage("lblAttackWithCard"); } return null; } diff --git a/forge-gui/src/main/java/forge/gamemodes/match/input/InputBlock.java b/forge-gui/src/main/java/forge/gamemodes/match/input/InputBlock.java index b0d04e6cf4a..d3a113ca7df 100644 --- a/forge-gui/src/main/java/forge/gamemodes/match/input/InputBlock.java +++ b/forge-gui/src/main/java/forge/gamemodes/match/input/InputBlock.java @@ -148,14 +148,14 @@ public class InputBlock extends InputSyncronizedBase { @Override public String getActivateAction(Card card) { if (combat.isAttacking(card)) { - return "declare blockers for card"; + return Localizer.getInstance().getMessage("lblDeclareBlockersForCard"); } if (currentAttacker != null && card.isCreature() && defender.getZone(ZoneType.Battlefield).contains(card)) { if (combat.isBlocking(card, currentAttacker)) { - return "remove card from combat"; + return Localizer.getInstance().getMessage("lblRemoveFromCombat"); } if (CombatUtil.canBlock(currentAttacker, card, combat)) { - return "block with card"; + return Localizer.getInstance().getMessage("lblBlockWithCard"); } } return null; diff --git a/forge-gui/src/main/java/forge/gamemodes/match/input/InputPayMana.java b/forge-gui/src/main/java/forge/gamemodes/match/input/InputPayMana.java index 20a1b70496d..5a8c92ddc63 100644 --- a/forge-gui/src/main/java/forge/gamemodes/match/input/InputPayMana.java +++ b/forge-gui/src/main/java/forge/gamemodes/match/input/InputPayMana.java @@ -136,7 +136,7 @@ public abstract class InputPayMana extends InputSyncronizedBase { public String getActivateAction(Card card) { for (SpellAbility sa : getAllManaAbilities(card)) { if (sa.canPlay()) { - return "pay mana with card"; + return Localizer.getInstance().getMessage("lblPayManaWithCard"); } } return null;