Merge pull request #2626 from tool4ever/fixnpe6

Fix Churning Reservoir NPE
This commit is contained in:
Anthony Calosa
2023-03-07 07:28:36 +08:00
committed by GitHub
11 changed files with 21 additions and 70 deletions

View File

@@ -358,7 +358,7 @@ public class DamageDealAi extends DamageAiBase {
return c.getSVar("Targeting").equals("Dies") return c.getSVar("Targeting").equals("Dies")
|| (ComputerUtilCombat.getEnoughDamageToKill(c, d, source, false, noPrevention) <= d) || (ComputerUtilCombat.getEnoughDamageToKill(c, d, source, false, noPrevention) <= d)
&& !ComputerUtil.canRegenerate(ai, c) && !ComputerUtil.canRegenerate(ai, c)
&& !(c.getSVar("SacMe").length() > 0) && !c.hasSVar("SacMe")
&& !ComputerUtilCard.hasActiveUndyingOrPersist(c); && !ComputerUtilCard.hasActiveUndyingOrPersist(c);
} }
}); });
@@ -437,7 +437,7 @@ public class DamageDealAi extends DamageAiBase {
return c.getSVar("Targeting").equals("Dies") return c.getSVar("Targeting").equals("Dies")
|| (ComputerUtilCombat.getEnoughDamageToKill(c, d, source, false, noPrevention) <= d) || (ComputerUtilCombat.getEnoughDamageToKill(c, d, source, false, noPrevention) <= d)
&& !ComputerUtil.canRegenerate(ai, c) && !ComputerUtil.canRegenerate(ai, c)
&& !(c.getSVar("SacMe").length() > 0); && !c.hasSVar("SacMe");
} }
}); });

View File

@@ -1460,20 +1460,6 @@ public class AbilityUtils {
if (unlessCost.equals("CardManaCost")) { if (unlessCost.equals("CardManaCost")) {
cost = new Cost(source.getManaCost(), true); 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")) { else if (unlessCost.equals("ChosenManaCost")) {
if (!source.hasChosenCard()) { if (!source.hasChosenCard()) {
cost = new Cost(ManaCost.ZERO, true); cost = new Cost(ManaCost.ZERO, true);
@@ -2734,12 +2720,12 @@ public class AbilityUtils {
// Count$ThisTurnEntered <ZoneDestination> [from <ZoneOrigin>] <Valid> // Count$ThisTurnEntered <ZoneDestination> [from <ZoneOrigin>] <Valid>
if (sq[0].startsWith("ThisTurnEntered")) { if (sq[0].startsWith("ThisTurnEntered")) {
final String[] workingCopy = l[0].split("_"); final String[] workingCopy = l[0].split("_", 5);
ZoneType destination = ZoneType.smartValueOf(workingCopy[1]); ZoneType destination = ZoneType.smartValueOf(workingCopy[1]);
final boolean hasFrom = workingCopy[2].equals("from"); final boolean hasFrom = workingCopy[2].equals("from");
ZoneType origin = hasFrom ? ZoneType.smartValueOf(workingCopy[3]) : null; ZoneType origin = hasFrom ? ZoneType.smartValueOf(workingCopy[3]) : null;
String validFilter = workingCopy[hasFrom ? 4 : 2] ; String validFilter = workingCopy[hasFrom ? 4 : 2];
final List<Card> res = CardUtil.getThisTurnEntered(destination, origin, validFilter, c, ctb); final List<Card> res = CardUtil.getThisTurnEntered(destination, origin, validFilter, c, ctb);
return doXMath(res.size(), expr, c, ctb); return doXMath(res.size(), expr, c, ctb);
@@ -2747,12 +2733,12 @@ public class AbilityUtils {
// Count$LastTurnEntered <ZoneDestination> [from <ZoneOrigin>] <Valid> // Count$LastTurnEntered <ZoneDestination> [from <ZoneOrigin>] <Valid>
if (sq[0].startsWith("LastTurnEntered")) { if (sq[0].startsWith("LastTurnEntered")) {
final String[] workingCopy = l[0].split("_"); final String[] workingCopy = l[0].split("_", 5);
ZoneType destination = ZoneType.smartValueOf(workingCopy[1]); ZoneType destination = ZoneType.smartValueOf(workingCopy[1]);
final boolean hasFrom = workingCopy[2].equals("from"); final boolean hasFrom = workingCopy[2].equals("from");
ZoneType origin = hasFrom ? ZoneType.smartValueOf(workingCopy[3]) : null; ZoneType origin = hasFrom ? ZoneType.smartValueOf(workingCopy[3]) : null;
String validFilter = workingCopy[hasFrom ? 4 : 2] ; String validFilter = workingCopy[hasFrom ? 4 : 2];
final List<Card> res = CardUtil.getLastTurnEntered(destination, origin, validFilter, c, ctb); final List<Card> res = CardUtil.getLastTurnEntered(destination, origin, validFilter, c, ctb);
return doXMath(res.size(), expr, c, ctb); return doXMath(res.size(), expr, c, ctb);

View File

@@ -905,7 +905,7 @@ public abstract class SpellAbilityEffect {
return activator.getController().chooseSingleEntityForEffect(options, sa, Localizer.getInstance().getMessage("lblChoosePlayer"), null); 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(); Card exilingSource = cause.getHostCard();
// during replacement LKI might be used // during replacement LKI might be used
if (cause.isReplacementAbility() && exilingSource.isLKI()) { if (cause.isReplacementAbility() && exilingSource.isLKI()) {

View File

@@ -1501,32 +1501,13 @@ public class CardProperty {
return false; 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")) { else if (property.startsWith("counters")) {
/* // syntax example: counters_GE9_P1P1 or counters_LT12_TIME
* 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
final String[] splitProperty = property.split("_"); final String[] splitProperty = property.split("_");
final String strNum = splitProperty[1].substring(2); final String strNum = splitProperty[1].substring(2);
final String comparator = splitProperty[1].substring(0, 2); final String comparator = splitProperty[1].substring(0, 2);
String counterType; final String counterType = splitProperty[2];
int number = AbilityUtils.calculateAmount(source, strNum, spellAbility); final int number = AbilityUtils.calculateAmount(source, strNum, spellAbility);
counterType = splitProperty[2];
final int actualnumber = card.getCounters(CounterType.getType(counterType)); final int actualnumber = card.getCounters(CounterType.getType(counterType));

View File

@@ -19,6 +19,7 @@ package forge.game.cost;
import forge.card.CardType; import forge.card.CardType;
import forge.game.Game; import forge.game.Game;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardCollectionView; import forge.game.card.CardCollectionView;
import forge.game.card.CardLists; import forge.game.card.CardLists;
@@ -187,11 +188,8 @@ public class CostExile extends CostPartWithList {
@Override @Override
protected Card doPayment(SpellAbility ability, Card targetCard, final boolean effect) { protected Card doPayment(SpellAbility ability, Card targetCard, final boolean effect) {
final Game game = targetCard.getGame(); final Game game = targetCard.getGame();
final Card host = ability.getHostCard();
Card newCard = game.getAction().exile(targetCard, null); Card newCard = game.getAction().exile(targetCard, null);
host.addExiledCard(newCard); SpellAbilityEffect.handleExiledWith(newCard, ability);
newCard.setExiledWith(host);
newCard.setExiledBy(ability.getActivatingPlayer());
return newCard; return newCard;
} }

View File

@@ -70,21 +70,11 @@ public class CostUnattach extends CostPartWithList {
*/ */
@Override @Override
public final boolean canPay(final SpellAbility ability, final Player payer, final boolean effect) { public final boolean canPay(final SpellAbility ability, final Player payer, final boolean effect) {
final Card source = ability.getHostCard(); return findCardToUnattach(ability.getHostCard(), payer, ability) != null;
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;
}
} }
public Card findCardToUnattach(final Card source, Player activator, SpellAbility ability) { public Card findCardToUnattach(final Card source, Player activator, SpellAbility ability) {
if (getType().equals("CARDNAME")) { if (payCostFromSource()) {
if (source.isEquipping()) { if (source.isEquipping()) {
return source; return source;
} }

View File

@@ -503,8 +503,7 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
if (thisHasFizzled) { // Fizzle if (thisHasFizzled) { // Fizzle
if (sa.isBestow()) { if (sa.isBestow()) {
// 702.102d: if its target is illegal, // 702.102e: if its target is illegal, the effect making it an Aura spell ends.
// the effect making it an Aura spell ends.
// It continues resolving as a creature spell. // It continues resolving as a creature spell.
source.unanimateBestow(); source.unanimateBestow();
SpellAbility first = source.getFirstSpellAbility(); SpellAbility first = source.getFirstSpellAbility();
@@ -834,10 +833,9 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
} }
if (activator.equals(activePlayer)) { if (activator.equals(activePlayer)) {
adjustAuraHost(sa);
activePlayerSAs.add(sa); activePlayerSAs.add(sa);
} }
adjustAuraHost(sa);
} }
simultaneousStackEntryList.removeAll(activePlayerSAs); simultaneousStackEntryList.removeAll(activePlayerSAs);

View File

@@ -2,7 +2,7 @@ Name:Churning Reservoir
ManaCost:R ManaCost:R
Types:Artifact Types:Artifact
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | Execute$ TrigPutCounter | TriggerZones$ Battlefield | TriggerDescription$ At the beginning of your upkeep, put an oil counter on another target nontoken artifact or creature you control. T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | Execute$ TrigPutCounter | TriggerZones$ Battlefield | TriggerDescription$ At the beginning of your upkeep, put an oil counter on another target nontoken artifact or creature you control.
SVar:TrigPutCounter:DB$ PutCounter | ValidTgts$ Artifact.nonToken+YouCtrl+Other,Creature.YouCtrl+Other+nonToken | CounterType$ OIL| CounterNum$ 1 SVar:TrigPutCounter:DB$ PutCounter | ValidTgts$ Artifact.nonToken+YouCtrl+Other,Creature.YouCtrl+Other+nonToken | CounterType$ OIL | CounterNum$ 1
A:AB$ Token | Cost$ 2 T | TokenScript$ r_1_1_phyrexian_goblin | CheckSVar$ CountCountersRemoved | CheckSVarCompare$ GE1 | SpellDescription$ Create a 1/1 red Phyrexian Goblin creature token. Activate only if an oil counter was removed from a permanent you controlled this turn or a permanent with an oil counter on it was put into a graveyard this turn. A:AB$ Token | Cost$ 2 T | TokenScript$ r_1_1_phyrexian_goblin | CheckSVar$ CountCountersRemoved | CheckSVarCompare$ GE1 | SpellDescription$ Create a 1/1 red Phyrexian Goblin creature token. Activate only if an oil counter was removed from a permanent you controlled this turn or a permanent with an oil counter on it was put into a graveyard this turn.
SVar:CountCountersRemoved:Count$CountersRemovedThisTurn OIL Card.YouCtrl+inRealZoneBattlefield/Plus.X SVar:CountCountersRemoved:Count$CountersRemovedThisTurn OIL Card.YouCtrl+inRealZoneBattlefield/Plus.X
SVar:X:Count$ThisTurnEntered_Graveyard_from_Battlefield_Permanent.YouCtrl+counters_GE1_OIL SVar:X:Count$ThisTurnEntered_Graveyard_from_Battlefield_Permanent.YouCtrl+counters_GE1_OIL

View File

@@ -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. 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 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. 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: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 SVar:HasAttackEffect:TRUE
DeckHints:Ability$Counters 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. 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.

View File

@@ -5,9 +5,9 @@ PT:1/1
K:etbCounter:P1P1:3 K:etbCounter:P1P1:3
K:Flying 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." 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<X/P1P1> | TokenAmount$ X | TokenScript$ c_1_1_a_tetravite_flying_noenchant | TokenOwner$ You | RememberTokens$ True SVar:TrigToken:AB$ Token | Cost$ SubCounter<X/P1P1> | TokenAmount$ X | TokenScript$ c_1_1_a_tetravite_flying_noenchant | TokenOwner$ You | RememberTokens$ True
SVar:X:Count$xPaid 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. 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<X/Creature.IsRemembered/Tetravite> | Defined$ Self | CounterType$ P1P1 | CounterNum$ X | CostDesc$ Exile any number of tokens put onto the battlefield with CARDNAME. SVar:TrigPutCounters:AB$ PutCounter | Cost$ Exile<X/Creature.IsRemembered/Tetravite> | Defined$ Self | CounterType$ P1P1 | CounterNum$ X | CostDesc$ Exile any number of tokens put onto the battlefield with CARDNAME.
DeckHas:Ability$Token|Counters 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. 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.

View File

@@ -379,8 +379,7 @@ public class HumanCostDecision extends CostDecisionMakerBase {
private PaymentDecision exileFromMiscZone(final CostExile cost, final SpellAbility sa, final int nNeeded, final CardCollection typeList) { private PaymentDecision exileFromMiscZone(final CostExile cost, final SpellAbility sa, final int nNeeded, final CardCollection typeList) {
if (typeList.size() < nNeeded) { return null; } if (typeList.size() < nNeeded) { return null; }
final List<ZoneType> origin = Lists.newArrayList(); final List<ZoneType> origin = Lists.newArrayList(cost.from);
origin.add(cost.from);
final CardCollection exiled = new CardCollection(); final CardCollection exiled = new CardCollection();
final List<Card> chosen = controller.chooseCardsForZoneChange(ZoneType.Exile, origin, sa, typeList, mandatory ? nNeeded : 0, final List<Card> chosen = controller.chooseCardsForZoneChange(ZoneType.Exile, origin, sa, typeList, mandatory ? nNeeded : 0,