From 4d4f89f4b7527268436a52ef8973e5466b040a7d Mon Sep 17 00:00:00 2001 From: tool4EvEr Date: Mon, 6 Mar 2023 20:05:07 +0100 Subject: [PATCH 1/3] Fix Churning Reservoir NPE --- .../java/forge/game/ability/AbilityUtils.java | 22 +++------------- .../java/forge/game/card/CardProperty.java | 25 +++---------------- .../java/forge/game/cost/CostUnattach.java | 14 ++--------- .../main/java/forge/game/zone/MagicStack.java | 6 ++--- .../res/cardsfolder/c/churning_reservoir.txt | 2 +- 5 files changed, 12 insertions(+), 57 deletions(-) diff --git a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java index be219421131..530d692c5b3 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java @@ -1460,20 +1460,6 @@ public class AbilityUtils { if (unlessCost.equals("CardManaCost")) { cost = new Cost(source.getManaCost(), true); } - else if (unlessCost.equals("TriggeredSpellManaCost")) { - SpellAbility triggered = (SpellAbility) sa.getRootAbility().getTriggeringObject(AbilityKey.SpellAbility); - Card triggeredCard = triggered.getHostCard(); - if (triggeredCard.getManaCost() == null) { - cost = new Cost(ManaCost.ZERO, true); - } else { - int xCount = triggeredCard.getManaCost().countX(); - int xPaid = triggeredCard.getXManaCostPaid() * xCount; - ManaCostBeingPaid toPay = new ManaCostBeingPaid(triggeredCard.getManaCost()); - toPay.decreaseShard(ManaCostShard.X, xCount); - toPay.increaseGenericMana(xPaid); - cost = new Cost(toPay.toManaCost(), true); - } - } else if (unlessCost.equals("ChosenManaCost")) { if (!source.hasChosenCard()) { cost = new Cost(ManaCost.ZERO, true); @@ -2734,12 +2720,12 @@ public class AbilityUtils { // Count$ThisTurnEntered [from ] if (sq[0].startsWith("ThisTurnEntered")) { - final String[] workingCopy = l[0].split("_"); + final String[] workingCopy = l[0].split("_", 5); ZoneType destination = ZoneType.smartValueOf(workingCopy[1]); final boolean hasFrom = workingCopy[2].equals("from"); ZoneType origin = hasFrom ? ZoneType.smartValueOf(workingCopy[3]) : null; - String validFilter = workingCopy[hasFrom ? 4 : 2] ; + String validFilter = workingCopy[hasFrom ? 4 : 2]; final List res = CardUtil.getThisTurnEntered(destination, origin, validFilter, c, ctb); return doXMath(res.size(), expr, c, ctb); @@ -2747,12 +2733,12 @@ public class AbilityUtils { // Count$LastTurnEntered [from ] if (sq[0].startsWith("LastTurnEntered")) { - final String[] workingCopy = l[0].split("_"); + final String[] workingCopy = l[0].split("_", 5); ZoneType destination = ZoneType.smartValueOf(workingCopy[1]); final boolean hasFrom = workingCopy[2].equals("from"); ZoneType origin = hasFrom ? ZoneType.smartValueOf(workingCopy[3]) : null; - String validFilter = workingCopy[hasFrom ? 4 : 2] ; + String validFilter = workingCopy[hasFrom ? 4 : 2]; final List res = CardUtil.getLastTurnEntered(destination, origin, validFilter, c, ctb); return doXMath(res.size(), expr, c, ctb); diff --git a/forge-game/src/main/java/forge/game/card/CardProperty.java b/forge-game/src/main/java/forge/game/card/CardProperty.java index 9c116ea71fb..398f463325a 100644 --- a/forge-game/src/main/java/forge/game/card/CardProperty.java +++ b/forge-game/src/main/java/forge/game/card/CardProperty.java @@ -1501,32 +1501,13 @@ public class CardProperty { return false; } } - - // syntax example: countersGE9 P1P1 or countersLT12TIME (greater number - // than 99 not supported) - /* - * slapshot5 - fair warning, you cannot use numbers with 2 digits - * (greater number than 9 not supported you can use X and the - * SVar:X:Number$12 to get two digits. This will need a better fix, and - * I have the beginnings of a regex below - */ else if (property.startsWith("counters")) { - /* - * Pattern p = Pattern.compile("[a-z]*[A-Z][A-Z][X0-9]+.*$"); - * String[] parse = ??? - * System.out.println("Parsing completed of: "+Property); for (int i - * = 0; i < parse.length; i++) { - * System.out.println("parse["+i+"]: "+parse[i]); } - */ - - // TODO get a working regex out of this pattern so the amount of - // digits doesn't matter + // syntax example: counters_GE9_P1P1 or counters_LT12_TIME final String[] splitProperty = property.split("_"); final String strNum = splitProperty[1].substring(2); final String comparator = splitProperty[1].substring(0, 2); - String counterType; - int number = AbilityUtils.calculateAmount(source, strNum, spellAbility); - counterType = splitProperty[2]; + final String counterType = splitProperty[2]; + final int number = AbilityUtils.calculateAmount(source, strNum, spellAbility); final int actualnumber = card.getCounters(CounterType.getType(counterType)); diff --git a/forge-game/src/main/java/forge/game/cost/CostUnattach.java b/forge-game/src/main/java/forge/game/cost/CostUnattach.java index 8c0626f1d46..3861837fd19 100644 --- a/forge-game/src/main/java/forge/game/cost/CostUnattach.java +++ b/forge-game/src/main/java/forge/game/cost/CostUnattach.java @@ -70,21 +70,11 @@ public class CostUnattach extends CostPartWithList { */ @Override public final boolean canPay(final SpellAbility ability, final Player payer, final boolean effect) { - final Card source = ability.getHostCard(); - - final String type = this.getType(); - if (type.equals("CARDNAME")) { - return source.isEquipping(); - } else if (type.equals("OriginalHost")) { - Card originalEquipment = ability.getOriginalHost(); - return originalEquipment.isEquipping(); - } else { - return CardLists.getValidCards(source.getEquippedBy(), type, payer, source, ability).size() > 0; - } + return findCardToUnattach(ability.getHostCard(), payer, ability) != null; } public Card findCardToUnattach(final Card source, Player activator, SpellAbility ability) { - if (getType().equals("CARDNAME")) { + if (payCostFromSource()) { if (source.isEquipping()) { return source; } diff --git a/forge-game/src/main/java/forge/game/zone/MagicStack.java b/forge-game/src/main/java/forge/game/zone/MagicStack.java index 4f26ab23c1f..488e7e3b952 100644 --- a/forge-game/src/main/java/forge/game/zone/MagicStack.java +++ b/forge-game/src/main/java/forge/game/zone/MagicStack.java @@ -503,8 +503,7 @@ public class MagicStack /* extends MyObservable */ implements Iterable Date: Mon, 6 Mar 2023 21:23:22 +0100 Subject: [PATCH 2/3] Fix trigger --- forge-gui/res/cardsfolder/f/forgehammer_centurion.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/forge-gui/res/cardsfolder/f/forgehammer_centurion.txt b/forge-gui/res/cardsfolder/f/forgehammer_centurion.txt index 3dafa6ed1f6..74b533c2e22 100644 --- a/forge-gui/res/cardsfolder/f/forgehammer_centurion.txt +++ b/forge-gui/res/cardsfolder/f/forgehammer_centurion.txt @@ -5,9 +5,8 @@ PT:3/2 T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Creature.YouCtrl+Other,Artifact.YouCtrl+Other | TriggerZones$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ Whenever another creature or artifact you control is put into a graveyard from the battlefield, put an oil counter on CARDNAME. SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ OIL | CounterNum$ 1 T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigImmediateTrig | TriggerDescription$ Whenever CARDNAME attacks, you may remove two oil counters from it. When you do, target creature can't block this turn. -SVar:TrigImmediateTrig:AB$ ImmediateTrigger | Cost$ SubCounter<2/OIL> | SubAbility$ TrigPump | TriggerDescription$ When you do, target creature can't block this turn. +SVar:TrigImmediateTrig:AB$ ImmediateTrigger | Cost$ SubCounter<2/OIL> | Execute$ TrigPump | TriggerDescription$ When you do, target creature can't block this turn. SVar:TrigPump:DB$ Pump | ValidTgts$ Creature | TgtPrompt$ Select target creature | IsCurse$ True | KW$ HIDDEN CARDNAME can't block. -SVar:X:Count$CardCounters.OIL SVar:HasAttackEffect:TRUE DeckHints:Ability$Counters Oracle:Whenever another creature or artifact you control is put into a graveyard from the battlefield, put an oil counter on Forgehammer Centurion.\nWhenever Forgehammer Centurion attacks, you may remove two oil counters from it. When you do, target creature can't block this turn. From 5c71c7e01bc00b31dce38090b008035a9204f6f5 Mon Sep 17 00:00:00 2001 From: tool4EvEr Date: Mon, 6 Mar 2023 22:29:18 +0100 Subject: [PATCH 3/3] Clean up --- forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java | 4 ++-- .../main/java/forge/game/ability/SpellAbilityEffect.java | 2 +- forge-game/src/main/java/forge/game/cost/CostExile.java | 6 ++---- forge-gui/res/cardsfolder/t/tetravus.txt | 4 ++-- forge-gui/src/main/java/forge/player/HumanCostDecision.java | 3 +-- 5 files changed, 8 insertions(+), 11 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java b/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java index 5119777c154..d6a2d2cbe30 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java @@ -358,7 +358,7 @@ public class DamageDealAi extends DamageAiBase { return c.getSVar("Targeting").equals("Dies") || (ComputerUtilCombat.getEnoughDamageToKill(c, d, source, false, noPrevention) <= d) && !ComputerUtil.canRegenerate(ai, c) - && !(c.getSVar("SacMe").length() > 0) + && !c.hasSVar("SacMe") && !ComputerUtilCard.hasActiveUndyingOrPersist(c); } }); @@ -437,7 +437,7 @@ public class DamageDealAi extends DamageAiBase { return c.getSVar("Targeting").equals("Dies") || (ComputerUtilCombat.getEnoughDamageToKill(c, d, source, false, noPrevention) <= d) && !ComputerUtil.canRegenerate(ai, c) - && !(c.getSVar("SacMe").length() > 0); + && !c.hasSVar("SacMe"); } }); diff --git a/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java b/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java index 95cf9e0700a..354ed3b8835 100644 --- a/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java +++ b/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java @@ -905,7 +905,7 @@ public abstract class SpellAbilityEffect { return activator.getController().chooseSingleEntityForEffect(options, sa, Localizer.getInstance().getMessage("lblChoosePlayer"), null); } - public void handleExiledWith(final Card movedCard, final SpellAbility cause) { + public static void handleExiledWith(final Card movedCard, final SpellAbility cause) { Card exilingSource = cause.getHostCard(); // during replacement LKI might be used if (cause.isReplacementAbility() && exilingSource.isLKI()) { diff --git a/forge-game/src/main/java/forge/game/cost/CostExile.java b/forge-game/src/main/java/forge/game/cost/CostExile.java index 275dbbd17e1..e14ce97c019 100644 --- a/forge-game/src/main/java/forge/game/cost/CostExile.java +++ b/forge-game/src/main/java/forge/game/cost/CostExile.java @@ -19,6 +19,7 @@ package forge.game.cost; import forge.card.CardType; import forge.game.Game; +import forge.game.ability.SpellAbilityEffect; import forge.game.card.Card; import forge.game.card.CardCollectionView; import forge.game.card.CardLists; @@ -187,11 +188,8 @@ public class CostExile extends CostPartWithList { @Override protected Card doPayment(SpellAbility ability, Card targetCard, final boolean effect) { final Game game = targetCard.getGame(); - final Card host = ability.getHostCard(); Card newCard = game.getAction().exile(targetCard, null); - host.addExiledCard(newCard); - newCard.setExiledWith(host); - newCard.setExiledBy(ability.getActivatingPlayer()); + SpellAbilityEffect.handleExiledWith(newCard, ability); return newCard; } diff --git a/forge-gui/res/cardsfolder/t/tetravus.txt b/forge-gui/res/cardsfolder/t/tetravus.txt index d297bb28d1a..23cda05fe71 100644 --- a/forge-gui/res/cardsfolder/t/tetravus.txt +++ b/forge-gui/res/cardsfolder/t/tetravus.txt @@ -5,9 +5,9 @@ PT:1/1 K:etbCounter:P1P1:3 K:Flying T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigToken | TriggerDescription$ At the beginning of your upkeep, you may remove any number of +1/+1 counters from CARDNAME. If you do, create that many 1/1 colorless Tetravite artifact creature tokens. They each have flying and "This creature can't be enchanted." -SVar:TrigToken:AB$Token | Cost$ SubCounter | TokenAmount$ X | TokenScript$ c_1_1_a_tetravite_flying_noenchant | TokenOwner$ You | RememberTokens$ True +SVar:TrigToken:AB$ Token | Cost$ SubCounter | TokenAmount$ X | TokenScript$ c_1_1_a_tetravite_flying_noenchant | TokenOwner$ You | RememberTokens$ True SVar:X:Count$xPaid T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigPutCounters | TriggerDescription$ At the beginning of your upkeep, you may exile any number of tokens created with CARDNAME. If you do, put that many +1/+1 counters on CARDNAME. -SVar:TrigPutCounters:AB$PutCounter | Cost$ Exile | Defined$ Self | CounterType$ P1P1 | CounterNum$ X | CostDesc$ Exile any number of tokens put onto the battlefield with CARDNAME. +SVar:TrigPutCounters:AB$ PutCounter | Cost$ Exile | Defined$ Self | CounterType$ P1P1 | CounterNum$ X | CostDesc$ Exile any number of tokens put onto the battlefield with CARDNAME. DeckHas:Ability$Token|Counters Oracle:Flying\nTetravus enters the battlefield with three +1/+1 counters on it.\nAt the beginning of your upkeep, you may remove any number of +1/+1 counters from Tetravus. If you do, create that many 1/1 colorless Tetravite artifact creature tokens. They each have flying and "This creature can't be enchanted."\nAt the beginning of your upkeep, you may exile any number of tokens created with Tetravus. If you do, put that many +1/+1 counters on Tetravus. diff --git a/forge-gui/src/main/java/forge/player/HumanCostDecision.java b/forge-gui/src/main/java/forge/player/HumanCostDecision.java index 477a3e2ce32..d38c8bdf1e1 100644 --- a/forge-gui/src/main/java/forge/player/HumanCostDecision.java +++ b/forge-gui/src/main/java/forge/player/HumanCostDecision.java @@ -379,8 +379,7 @@ public class HumanCostDecision extends CostDecisionMakerBase { private PaymentDecision exileFromMiscZone(final CostExile cost, final SpellAbility sa, final int nNeeded, final CardCollection typeList) { if (typeList.size() < nNeeded) { return null; } - final List origin = Lists.newArrayList(); - origin.add(cost.from); + final List origin = Lists.newArrayList(cost.from); final CardCollection exiled = new CardCollection(); final List chosen = controller.chooseCardsForZoneChange(ZoneType.Exile, origin, sa, typeList, mandatory ? nNeeded : 0,