* Fix crash

* Move check earlier so Full Control doesn't ask for non-matches

* Restore fix

* Clean up duration checks
This commit is contained in:
tool4ever
2024-01-29 12:50:43 +01:00
committed by GitHub
parent 2cffe0c172
commit ad2039d5a1
16 changed files with 52 additions and 68 deletions

View File

@@ -313,6 +313,7 @@ public class PumpAi extends PumpAiBase {
attack = root.getXManaCostPaid();
}
} else {
// TODO add Double
attack = AbilityUtils.calculateAmount(sa.getHostCard(), numAttack, sa);
if (numAttack.contains("X") && sa.getSVar("X").equals("Count$CardsInYourHand") && source.isInZone(ZoneType.Hand)) {
attack--; // the card will be spent casting the spell, so actual power is 1 less

View File

@@ -2733,7 +2733,7 @@ public class AbilityUtils {
// Count$ThisTurnEntered <ZoneDestination> [from <ZoneOrigin>] <Valid>
if (sq[0].startsWith("ThisTurnEntered") || sq[0].startsWith("LastTurnEntered")) {
final String[] workingCopy = paidparts[0].split("_");
final String[] workingCopy = paidparts[0].split("_", 5);
ZoneType destination = ZoneType.smartValueOf(workingCopy[1]);
final boolean hasFrom = workingCopy[2].equals("from");
ZoneType origin = hasFrom ? ZoneType.smartValueOf(workingCopy[3]) : null;

View File

@@ -930,6 +930,27 @@ public abstract class SpellAbilityEffect {
}
}
protected static boolean checkValidDuration(String duration, SpellAbility sa) {
if (duration == null) {
return true;
}
Card hostCard = sa.getHostCard();
//if host is not on the battlefield don't apply
// Suspend should does Affect the Stack
if ((duration.startsWith("UntilHostLeavesPlay") || "UntilLoseControlOfHost".equals(duration) || "UntilUntaps".equals(duration))
&& !(hostCard.isInPlay() || hostCard.isInZone(ZoneType.Stack))) {
return false;
}
if ("UntilLoseControlOfHost".equals(duration) && hostCard.getController() != sa.getActivatingPlayer()) {
return false;
}
if ("UntilUntaps".equals(duration) && !hostCard.isTapped()) {
return false;
}
return true;
}
public static Player getNewChooser(final SpellAbility sa, final Player activator, final Player loser) {
// CR 800.4g
final PlayerCollection options;

View File

@@ -1,6 +1,5 @@
package forge.game.ability.effects;
import com.google.common.collect.Lists;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.card.CardCollection;
@@ -8,13 +7,12 @@ import forge.game.spellability.SpellAbility;
import forge.util.Lang;
import forge.util.TextUtil;
import java.util.ArrayList;
public class AlterAttributeEffect extends SpellAbilityEffect {
@Override
public void resolve(SpellAbility sa) {
boolean activate = Boolean.valueOf(sa.getParamOrDefault("Activate", "true"));
ArrayList<String> attributes = Lists.newArrayList(sa.getParam("Attributes").split(","));
String[] attributes = sa.getParam("Attributes").split(",");
CardCollection defined = getDefinedCardsOrTargeted(sa, "Defined");
if (sa.hasParam("Optional")) {
@@ -28,9 +26,9 @@ public class AlterAttributeEffect extends SpellAbilityEffect {
}
}
for(Card c : defined) {
for(String attr : attributes) {
switch(attr.trim()) {
for (Card c : defined) {
for (String attr : attributes) {
switch (attr.trim()) {
case "Solve":
case "Solved":
c.setSolved(activate);

View File

@@ -31,12 +31,7 @@ public class AnimateEffect extends AnimateEffectBase {
String animateRemembered = null;
String animateImprinted = null;
//if host is not on the battlefield don't apply
if (("UntilHostLeavesPlay".equals(duration) || "UntilLoseControlOfHost".equals(duration))
&& !source.isInPlay()) {
return;
}
if ("UntilLoseControlOfHost".equals(duration) && source.getController() != sa.getActivatingPlayer()) {
if (!checkValidDuration(duration, sa)) {
return;
}

View File

@@ -45,8 +45,7 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect {
public void resolve(SpellAbility sa) {
final Card source = sa.getHostCard();
//if host is not on the battlefield don't apply
if ("UntilHostLeavesPlay".equals(sa.getParam("Duration")) && !source.isInPlay()) {
if (!checkValidDuration(sa.getParam("Duration"), sa)) {
return;
}

View File

@@ -428,8 +428,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
@Override
public void resolve(SpellAbility sa) {
//if host is not on the battlefield don't apply
if ("UntilHostLeavesPlay".equals(sa.getParam("Duration")) && !sa.getHostCard().isInPlay()) {
if (!checkValidDuration(sa.getParam("Duration"), sa)) {
return;
}

View File

@@ -128,10 +128,9 @@ public class CloneEffect extends SpellAbilityEffect {
for (Card tgtCard : cloneTargets) {
game.getTriggerHandler().clearActiveTriggers(tgtCard, null);
if (sa.hasParam("CloneZone")) {
if (!tgtCard.isInZone(ZoneType.smartValueOf(sa.getParam("CloneZone")))) {
continue;
}
if (sa.hasParam("CloneZone") &&
!tgtCard.isInZone(ZoneType.smartValueOf(sa.getParam("CloneZone")))) {
continue;
}
if (tgtCard.isPhasedOut()) {

View File

@@ -55,14 +55,7 @@ public class EffectEffect extends SpellAbilityEffect {
String noteCounterDefined = null;
final String duration = sa.getParam("Duration");
if (((duration != null && duration.startsWith("UntilHostLeavesPlay")) || "UntilLoseControlOfHost".equals(duration) || "UntilUntaps".equals(duration))
&& !(hostCard.isInPlay() || hostCard.isInZone(ZoneType.Stack))) {
return;
}
if ("UntilLoseControlOfHost".equals(duration) && hostCard.getController() != sa.getActivatingPlayer()) {
return;
}
if ("UntilUntaps".equals(duration) && !hostCard.isTapped()) {
if (!checkValidDuration(duration, sa)) {
return;
}

View File

@@ -128,6 +128,10 @@ public class PumpAllEffect extends SpellAbilityEffect {
final List<ZoneType> affectedZones = Lists.newArrayList();
final Game game = sa.getActivatingPlayer().getGame();
if (!checkValidDuration(sa.getParam("Duration"), sa)) {
return;
}
if (sa.hasParam("PumpZone")) {
affectedZones.addAll(ZoneType.listValueOf(sa.getParam("PumpZone")));
} else {

View File

@@ -44,16 +44,6 @@ public class PumpEffect extends SpellAbilityEffect {
final String duration = sa.getParam("Duration");
final boolean perpetual = ("Perpetual").equals(duration);
//if host is not on the battlefield don't apply
// Suspend should does Affect the Stack
if (((duration != null && duration.startsWith("UntilHostLeavesPlay")) || "UntilLoseControlOfHost".equals(duration))
&& !(host.isInPlay() || host.isInZone(ZoneType.Stack))) {
return;
}
if ("UntilLoseControlOfHost".equals(duration) && host.getController() != sa.getActivatingPlayer()) {
return;
}
// do Game Check there in case of LKI
final Card gameCard = game.getCardState(applyTo, null);
if (gameCard == null || !applyTo.equalsWithTimestamp(gameCard)) {
@@ -152,13 +142,6 @@ public class PumpEffect extends SpellAbilityEffect {
final Card host = sa.getHostCard();
final String duration = sa.getParam("Duration");
//if host is not on the battlefield don't apply
// Suspend should does Affect the Stack
if (((duration != null && duration.startsWith("UntilHostLeavesPlay")) || "UntilLoseControlOfHost".equals(duration))
&& !(host.isInPlay() || host.isInZone(ZoneType.Stack))) {
return;
}
if (!keywords.isEmpty()) {
p.addChangedKeywords(keywords, ImmutableList.of(), timestamp, 0);
}
@@ -294,6 +277,10 @@ public class PumpEffect extends SpellAbilityEffect {
@Override
public void resolve(final SpellAbility sa) {
if (!checkValidDuration(sa.getParam("Duration"), sa)) {
return;
}
final Player activator = sa.getActivatingPlayer();
final Game game = activator.getGame();
final Card host = sa.getHostCard();

View File

@@ -200,7 +200,7 @@ public class CostAdjustment {
// Sort abilities to apply them in proper order
for (Card c : cardsOnBattlefield) {
for (final StaticAbility stAb : c.getStaticAbilities()) {
if (stAb.checkMode("ReduceCost")) {
if (stAb.checkMode("ReduceCost") && checkRequirement(sa, stAb)) {
reduceAbilities.add(stAb);
}
else if (stAb.checkMode("SetCost")) {
@@ -401,10 +401,6 @@ public class CostAdjustment {
final Card card = sa.getHostCard();
final String amount = staticAbility.getParam("Amount");
if (!checkRequirement(sa, staticAbility)) {
return 0;
}
int value;
if ("AffectedX".equals(amount)) {
value = AbilityUtils.calculateAmount(card, amount, staticAbility);

View File

@@ -138,7 +138,7 @@ public class CostPartMana extends CostPart {
if (isCostPayAnyNumberOfTimes) {
int timesToPay = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getSVar("NumTimes"), sa);
if (timesToPay == 0) {
return null;
return ManaCost.NO_COST;
}
ManaCostBeingPaid totalMana = new ManaCostBeingPaid(getMana());
for (int i = 1; i < timesToPay; i++) {

View File

@@ -3,13 +3,11 @@ ManaCost:2 R R
Types:Enchantment Aura
K:Enchant creature you control
A:SP$ Attach | Cost$ 2 R R | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control to attach Breath of Fury to | AILogic$ Pump
T:Mode$ DamageDone | ValidSource$ Card.AttachedBy | ValidTarget$ Player | CombatDamage$ True | Execute$ NewFuriousLeader | TriggerZones$ Battlefield | TriggerDescription$ When enchanted creature deals combat damage to a player, sacrifice it and attach CARDNAME to a creature you control. If you do, untap all creatures you control and after this phase, there is an additional combat phase.
SVar:NewFuriousLeader:DB$ ChooseCard | Defined$ You | Amount$ 1 | Choices$ Creature.NotEnchantedBy+YouCtrl+CanBeEnchantedBy | ChoiceTitle$ Choose another creature you control to attach Breath of Fury to | SubAbility$ TrigSacrifice
SVar:TrigSacrifice:DB$ SacrificeAll | ValidCards$ Card.EnchantedBy | SubAbility$ StillFurious | RememberSacrificed$ True
SVar:StillFurious:DB$ Attach | Defined$ ChosenCard | ConditionCheckSVar$ WasSacced | ConditionSVarCompare$ EQ1 | SubAbility$ Cleanup
SVar:Cleanup:DB$ Cleanup | ClearRemembered$ True | SubAbility$ CatchBreath
SVar:CatchBreath:DB$ UntapAll | ValidCards$ Creature.YouCtrl | SubAbility$ TheFuryContinues
SVar:TheFuryContinues:DB$ AddPhase | ExtraPhase$ Combat | AfterPhase$ EndCombat
SVar:WasSacced:Remembered$Amount
T:Mode$ DamageDone | ValidSource$ Card.AttachedBy | ValidTarget$ Player | CombatDamage$ True | Execute$ TrigSacrifice | TriggerZones$ Battlefield | TriggerDescription$ When enchanted creature deals combat damage to a player, sacrifice it and attach CARDNAME to a creature you control. If you do, untap all creatures you control and after this phase, there is an additional combat phase.
SVar:TrigSacrifice:DB$ SacrificeAll | ValidCards$ Card.EnchantedBy | SubAbility$ StillFurious
SVar:StillFurious:DB$ Attach | Object$ Self | Choices$ Creature.YouCtrl | ChoiceTitle$ Choose a creature you control to attach Breath of Fury to | RememberAttached$ True | SubAbility$ CatchBreath
SVar:CatchBreath:DB$ UntapAll | ValidCards$ Creature.YouCtrl | SubAbility$ TheFuryContinues | ConditionDefined$ Rememembered | ConditionPresent$ Card
SVar:TheFuryContinues:DB$ AddPhase | ExtraPhase$ Combat | AfterPhase$ EndCombat | ConditionDefined$ Remembered | ConditionPresent$ Card | SubAbility$ Cleanup
SVar:Cleanup:DB$ Cleanup | ClearRemembered$ True
AI:RemoveDeck:All
Oracle:Enchant creature you control\nWhen enchanted creature deals combat damage to a player, sacrifice it and attach Breath of Fury to a creature you control. If you do, untap all creatures you control and after this phase, there is an additional combat phase.

View File

@@ -2,8 +2,5 @@ Name:The Blackstaff of Waterdeep
ManaCost:U
Types:Legendary Artifact
K:You may choose not to untap CARDNAME during your untap step.
A:AB$ Pump | Cost$ 1 U T | ValidTgts$ Artifact.Other+nonToken+YouCtrl | TgtPrompt$ Select another target nontoken artifact you control | RememberTargets$ True | AILogic$ ContinuousBonus | PrecostDesc$ Animate Walking Statue — | SpellDescription$ Another target nontoken artifact you control becomes a 4/4 artifact creature for as long as CARDNAME remains tapped. Activate only as a sorcery. | SorcerySpeed$ True | StackDescription$ SpellDescription
S:Mode$ Continuous | Affected$ Artifact.IsRemembered | SetPower$ 4 | SetToughness$ 4 | AddType$ Creature
T:Mode$ Untaps | ValidCard$ Card.Self | TriggerZones$ Battlefield | Execute$ ClearRemembered | Static$ True
SVar:ClearRemembered:DB$ Cleanup | ClearRemembered$ True
A:AB$ Animate | Cost$ 1 U T | ValidTgts$ Artifact.Other+nonToken+YouCtrl | TgtPrompt$ Select another target nontoken artifact you control | Duration$ UntilUntaps | Power$ 4 | Toughness$ 4 | Types$ Creature | PrecostDesc$ Animate Walking Statue — | SpellDescription$ Another target nontoken artifact you control becomes a 4/4 artifact creature for as long as CARDNAME remains tapped. Activate only as a sorcery. | SorcerySpeed$ True | StackDescription$ SpellDescription
Oracle:You may choose not to untap The Blackstaff of Waterdeep during your untap step.\nAnimate Walking Statue — {1}{U}, {T}: Another target nontoken artifact you control becomes a 4/4 artifact creature for as long as The Blackstaff of Waterdeep remains tapped. Activate only as a sorcery.

View File

@@ -3,9 +3,6 @@ ManaCost:4
Types:Artifact
K:Echo:4
K:You may choose not to untap CARDNAME during your untap step.
A:AB$ PumpAll | Cost$ 2 T | ValidCards$ Creature | RememberPumped$ True | SpellDescription$ All creatures get +2/+2 for as long as CARDNAME remains tapped.
S:Mode$ Continuous | Affected$ Creature.IsRemembered | AddPower$ 2 | AddToughness$ 2
T:Mode$ Untaps | ValidCard$ Card.Self | TriggerZones$ Battlefield | Execute$ ClearRemembered | Static$ True
SVar:ClearRemembered:DB$ Cleanup | ClearRemembered$ True
A:AB$ PumpAll | Cost$ 2 T | ValidCards$ Creature | Duration$ UntilUntaps | NumAtt$ 2 | NumDef$ 2 | SpellDescription$ All creatures get +2/+2 for as long as CARDNAME remains tapped.
AI:RemoveDeck:All
Oracle:Echo {4} (At the beginning of your upkeep, if this came under your control since the beginning of your last upkeep, sacrifice it unless you pay its echo cost.)\nYou may choose not to untap Thran Weaponry during your untap step.\n{2}, {T}: All creatures get +2/+2 for as long as Thran Weaponry remains tapped.