From f830c3a6b849aa07deaae6a980ba53c2987b930e Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Wed, 22 Jul 2020 14:32:25 +0000 Subject: [PATCH] Cardfactory cleanup --- .../src/main/java/forge/ai/ComputerUtil.java | 9 + .../java/forge/game/ability/AbilityUtils.java | 5 +- .../game/ability/effects/PlayEffect.java | 3 +- .../game/ability/effects/TokenEffectBase.java | 23 +- .../src/main/java/forge/game/card/Card.java | 9 +- .../java/forge/game/card/CardFactory.java | 6 - .../java/forge/game/card/CardFactoryUtil.java | 293 ++++++++++-------- .../game/spellability/AlternativeCost.java | 1 + .../forge/game/spellability/SpellAbility.java | 4 + .../StaticAbilityContinuous.java | 13 +- .../forge/player/HumanPlaySpellAbility.java | 1 - 11 files changed, 211 insertions(+), 156 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtil.java b/forge-ai/src/main/java/forge/ai/ComputerUtil.java index 76d4e326a31..bc1cc527832 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtil.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtil.java @@ -1053,6 +1053,12 @@ public class ComputerUtil { } } } + + if (ApiType.PermanentNoncreature.equals(sa.getApi()) && buffedcard.hasKeyword(Keyword.PROWESS)) { + // non creature Permanent spell + return true; + } + if (card.hasKeyword(Keyword.SOULBOND) && buffedcard.isCreature() && !buffedcard.isPaired()) { return true; } @@ -1219,6 +1225,9 @@ public class ComputerUtil { return true; } } + if (ApiType.PermanentNoncreature.equals(sa.getApi()) && buffedCard.hasKeyword(Keyword.PROWESS)) { + return true; + } //Fill the graveyard for Threshold if (checkThreshold) { for (StaticAbility stAb : buffedCard.getStaticAbilities()) { 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 828e9becae4..939d23a9e35 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java @@ -1915,10 +1915,7 @@ public class AbilityUtils { } public static final String getSVar(final CardTraitBase ability, final String sVarName) { - String val = null; - if (ability instanceof SpellAbility) { - val = ability.getSVar(sVarName); - } + String val = ability.getSVar(sVarName); if (StringUtils.isEmpty(val)) { Card host = null; if (ability instanceof SpellAbility) { diff --git a/forge-game/src/main/java/forge/game/ability/effects/PlayEffect.java b/forge-game/src/main/java/forge/game/ability/effects/PlayEffect.java index 780d6b8697e..65ef6e1f37a 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/PlayEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/PlayEffect.java @@ -24,6 +24,7 @@ import forge.game.player.Player; import forge.game.replacement.ReplacementEffect; import forge.game.replacement.ReplacementHandler; import forge.game.replacement.ReplacementLayer; +import forge.game.spellability.AlternativeCost; import forge.game.spellability.SpellAbility; import forge.game.trigger.TriggerType; import forge.game.zone.Zone; @@ -247,7 +248,7 @@ public class PlayEffect extends SpellAbilityEffect { } if (sa.hasParam("Madness")) { - tgtSA.getHostCard().setMadness(true); + tgtSA.setAlternativeCost(AlternativeCost.Madness); } if (tgtSA.usesTargeting() && !optional) { diff --git a/forge-game/src/main/java/forge/game/ability/effects/TokenEffectBase.java b/forge-game/src/main/java/forge/game/ability/effects/TokenEffectBase.java index 6768eb14849..52ec2a03853 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/TokenEffectBase.java +++ b/forge-game/src/main/java/forge/game/ability/effects/TokenEffectBase.java @@ -125,18 +125,25 @@ public abstract class TokenEffectBase extends SpellAbilityEffect { if (sa.hasParam("TokenAttacking") && combat.getAttackingPlayer().equals(controller)) { String attacking = sa.getParam("TokenAttacking"); GameEntity defender = null; + FCollectionView defs = null; if ("True".equalsIgnoreCase(attacking)) { - // into battlefield attacking only should work if you are the attacking player - if (combat.getAttackingPlayer().equals(controller)) { - final FCollectionView defs = combat.getDefenders(); - Map params = Maps.newHashMap(); - params.put("Attacker", c); - defender = controller.getController().chooseSingleEntityForEffect(defs, sa, - Localizer.getInstance().getMessage("lblChooseDefenderToAttackWithCard", CardTranslation.getTranslatedName(c.getName())), false, params); + defs = combat.getDefenders(); + } else if (sa.hasParam("ChoosePlayerOrPlaneswalker")) { + Player defendingPlayer = Iterables.getFirst(AbilityUtils.getDefinedPlayers(host, attacking, sa), null); + if (defendingPlayer != null) { + defs = game.getCombat().getDefendersControlledBy(defendingPlayer); } } else { - defender = Iterables.getFirst(AbilityUtils.getDefinedEntities(host, attacking, sa), null); + defs = AbilityUtils.getDefinedEntities(host, attacking, sa); } + + if (defs != null) { + Map params = Maps.newHashMap(); + params.put("Attacker", c); + defender = controller.getController().chooseSingleEntityForEffect(defs, sa, + Localizer.getInstance().getMessage("lblChooseDefenderToAttackWithCard", CardTranslation.getTranslatedName(c.getName())), false, params); + } + if (defender != null) { combat.addAttacker(c, defender); combatChanged = true; diff --git a/forge-game/src/main/java/forge/game/card/Card.java b/forge-game/src/main/java/forge/game/card/Card.java index 989d0041313..c98fc98df5f 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -182,7 +182,6 @@ public class Card extends GameEntity implements Comparable { private boolean tributed = false; private boolean embalmed = false; private boolean eternalized = false; - private boolean madness = false; private boolean madnessWithoutCast = false; private boolean flipped = false; @@ -5268,10 +5267,10 @@ public class Card extends GameEntity implements Comparable { } public boolean isMadness() { - return madness; - } - public void setMadness(boolean madness0) { - madness = madness0; + if (this.getCastSA() == null) { + return false; + } + return getCastSA().isMadness(); } public boolean getMadnessWithoutCast() { return madnessWithoutCast; } public void setMadnessWithoutCast(boolean state) { madnessWithoutCast = state; } diff --git a/forge-game/src/main/java/forge/game/card/CardFactory.java b/forge-game/src/main/java/forge/game/card/CardFactory.java index dd9f68bd571..22e500ee9cb 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactory.java +++ b/forge-game/src/main/java/forge/game/card/CardFactory.java @@ -36,7 +36,6 @@ import forge.game.staticability.StaticAbility; import forge.game.trigger.Trigger; import forge.game.trigger.TriggerHandler; import forge.game.trigger.WrappedAbility; -import forge.game.zone.ZoneType; import forge.item.IPaperCard; import forge.item.PaperCard; import forge.util.TextUtil; @@ -153,11 +152,6 @@ public class CardFactory { c.setXManaCostPaidByColor(original.getXManaCostPaidByColor()); c.setKickerMagnitude(original.getKickerMagnitude()); - // Rule 706.10 : Madness is copied - if (original.isInZone(ZoneType.Stack)) { - c.setMadness(original.isMadness()); - } - for (OptionalCost cost : original.getOptionalCostsPaid()) { c.addOptionalCostPaid(cost); } diff --git a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java index 770a767d3f4..7e196ff891c 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java @@ -1371,10 +1371,9 @@ public class CardFactoryUtil { // Count$Madness.. if (sq[0].startsWith("Madness")) { - if (c.isMadness()) { - return doXMath(StringUtils.isNumeric(sq[1]) ? Integer.parseInt(sq[1]) : xCount(c, c.getSVar(sq[1])), m, c); - } - return doXMath(StringUtils.isNumeric(sq[2]) ? Integer.parseInt(sq[2]) : xCount(c, c.getSVar(sq[2])), m, c); + String v = c.isMadness() ? sq[1] : sq[2]; + // TODO move this to AbilityUtils + return doXMath(StringUtils.isNumeric(v) ? Integer.parseInt(v) : xCount(c, c.getSVar(v)), m, c); } // Count$Presence_.. @@ -2173,7 +2172,6 @@ public class CardFactoryUtil { + "| Secondary$ True | Description$ " + desc + (!extraparams.equals("") ? " | " + extraparams : ""); ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, card, intrinsic); - re.setLayer(ReplacementLayer.Other); re.setOverridingAbility(sa); @@ -2469,15 +2467,21 @@ public class CardFactoryUtil { inst.addTrigger(trigger); } else if (keyword.equals("Extort")) { final String extortTrigger = "Mode$ SpellCast | ValidCard$ Card | ValidActivatingPlayer$ You | " - + "TriggerZones$ Battlefield | Execute$ ExtortOpps | Secondary$ True" + + "TriggerZones$ Battlefield | Secondary$ True" + " | TriggerDescription$ Extort ("+ inst.getReminderText() +")"; - final String abString = "AB$ LoseLife | Cost$ WB | Defined$ Player.Opponent | " - + "LifeAmount$ 1 | SubAbility$ ExtortGainLife"; - final String dbString = "DB$ GainLife | Defined$ You | LifeAmount$ AFLifeLost | References$ AFLifeLost"; + + final String loseLifeStr = "AB$ LoseLife | Cost$ WB | Defined$ Player.Opponent | LifeAmount$ 1"; + final String gainLifeStr = "DB$ GainLife | Defined$ You | LifeAmount$ AFLifeLost"; + + SpellAbility loseLifeSA = AbilityFactory.getAbility(loseLifeStr, card); + + AbilitySub gainLifeSA = (AbilitySub) AbilityFactory.getAbility(gainLifeStr, card); + gainLifeSA.setSVar("AFLifeLost", "Number$0"); + loseLifeSA.setSubAbility(gainLifeSA); + loseLifeSA.setIntrinsic(intrinsic); + final Trigger parsedTrigger = TriggerHandler.parseTrigger(extortTrigger, card, intrinsic); - card.setSVar("ExtortOpps", abString); - card.setSVar("ExtortGainLife", dbString); - card.setSVar("AFLifeLost", "Number$0"); + parsedTrigger.setOverridingAbility(loseLifeSA); inst.addTrigger(parsedTrigger); } else if (keyword.startsWith("Fabricate")) { final String[] k = keyword.split(":"); @@ -2511,17 +2515,24 @@ public class CardFactoryUtil { inst.addTrigger(trigger); } else if (keyword.startsWith("Fading")) { - String upkeepTrig = "Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield " + - " | Execute$ TrigUpkeepFading | Secondary$ True | TriggerDescription$ At the beginning of " + - "your upkeep, remove a fade counter from CARDNAME. If you can't, sacrifice CARDNAME."; + String upkeepTrig = "Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Secondary$ True " + + " | TriggerDescription$ At the beginning of your upkeep, remove a fade counter from CARDNAME. If you can't, sacrifice CARDNAME."; + + final String removeCounterStr = "DB$ RemoveCounter | Defined$ Self | CounterType$ FADE | CounterNum$ 1 | RememberRemoved$ True"; + final String sacrificeStr = "DB$ Sacrifice | SacValid$ Self | ConditionCheckSVar$ FadingCheckSVar | ConditionSVarCompare$ EQ0"; + final String cleanupStr = "DB$ Cleanup | ClearRemembered$ True"; + + SpellAbility removeCounterSA = AbilityFactory.getAbility(removeCounterStr, card); + AbilitySub sacrificeSA = (AbilitySub) AbilityFactory.getAbility(sacrificeStr, card); + sacrificeSA.setSVar("FadingCheckSVar","Count$RememberedSize"); + removeCounterSA.setSubAbility(sacrificeSA); + + AbilitySub cleanupSA = (AbilitySub) AbilityFactory.getAbility(cleanupStr, card); + sacrificeSA.setSubAbility(cleanupSA); - card.setSVar("TrigUpkeepFading", "AB$ RemoveCounter | Cost$ 0 | Defined$ Self | CounterType$ FADE" + - " | CounterNum$ 1 | RememberRemoved$ True | SubAbility$ DBUpkeepFadingSac"); - card.setSVar("DBUpkeepFadingSac","DB$ Sacrifice | SacValid$ Self | ConditionCheckSVar$ FadingCheckSVar" + - " | ConditionSVarCompare$ EQ0 | References$ FadingCheckSVar | SubAbility$ FadingCleanup"); - card.setSVar("FadingCleanup","DB$ Cleanup | ClearRemembered$ True"); - card.setSVar("FadingCheckSVar","Count$RememberedSize"); final Trigger trigger = TriggerHandler.parseTrigger(upkeepTrig, card, intrinsic); + removeCounterSA.setIntrinsic(intrinsic); + trigger.setOverridingAbility(removeCounterSA); inst.addTrigger(trigger); } else if (keyword.equals("Flanking")) { @@ -2650,25 +2661,42 @@ public class CardFactoryUtil { inst.addTrigger(trigger); } } else if (keyword.equals("Hideaway")) { + // The exiled card gains ‘Any player who has controlled the permanent that exiled this card may look at this card in the exile zone.’ + // this is currently not possible because the StaticAbility currently has no information about the OriginalHost + List triggers = Lists.newArrayList(); StringBuilder sb = new StringBuilder(); - sb.append("Mode$ ChangesZone | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigHideawayDig"); - sb.append("| Secondary$ True | TriggerDescription$ When CARDNAME enters the battlefield, "); + sb.append("Mode$ ChangesZone | Destination$ Battlefield | ValidCard$ Card.Self | Secondary$ True "); + sb.append("| TriggerDescription$ When CARDNAME enters the battlefield, "); sb.append("look at the top four cards of your library, exile one face down"); sb.append(", then put the rest on the bottom of your library."); final Trigger hideawayTrigger = TriggerHandler.parseTrigger(sb.toString(), card, intrinsic); + + String hideawayDig = "DB$ Dig | Defined$ You | DigNum$ 4 | DestinationZone$ Exile | ExileFaceDown$ True | RememberChanged$ True"; + String hideawayEffect = "DB$ Effect | StaticAbilities$ STHideawayEffectLookAtCard | ForgetOnMoved$ Exile | RememberObjects$ Remembered | Duration$ Permanent"; + + String lookAtCard = "Mode$ Continuous | Affected$ Card.IsRemembered | MayLookAt$ True | EffectZone$ Command | AffectedZone$ Exile | Description$ You may look at the exiled card."; + + SpellAbility digSA = AbilityFactory.getAbility(hideawayDig, card); + + AbilitySub effectSA = (AbilitySub) AbilityFactory.getAbility(hideawayEffect, card); + effectSA.setSVar("STHideawayEffectLookAtCard", lookAtCard); + + digSA.setSubAbility((AbilitySub)effectSA.copy()); + + hideawayTrigger.setOverridingAbility(digSA); + triggers.add(hideawayTrigger); - card.setSVar("TrigHideawayDig", "DB$ Dig | Defined$ You | DigNum$ 4 | DestinationZone$ Exile | ExileFaceDown$ True | RememberChanged$ True | SubAbility$ DBHideawayEffect"); - final Trigger gainControlTrigger = TriggerHandler.parseTrigger("Mode$ ChangesController | ValidCard$ Card.Self | Execute$ DBHideawayEffect | Static$ True", card, intrinsic); + + final Trigger gainControlTrigger = TriggerHandler.parseTrigger("Mode$ ChangesController | ValidCard$ Card.Self | Static$ True", card, intrinsic); + gainControlTrigger.setOverridingAbility((AbilitySub)effectSA.copy()); triggers.add(gainControlTrigger); - card.setSVar("DBHideawayEffect", "DB$ Effect | StaticAbilities$ STHideawayEffectLookAtCard | Triggers$ THideawayEffectCleanup | SVars$ DBHideawayEffectExileSelf | ImprintOnHost$ True | Duration$ Permanent | SubAbility$ DBHideawayRemember"); - card.setSVar("STHideawayEffectLookAtCard", "Mode$ Continuous | Affected$ Card.IsRemembered | MayLookAt$ True | EffectZone$ Command | AffectedZone$ Exile | Description$ You may look at the exiled card."); - card.setSVar("THideawayEffectCleanup", "Mode$ ChangesZone | ValidCard$ Card.IsRemembered | Origin$ Exile | Destination$ Any | TriggerZone$ Command | Execute$ DBHideawayEffectExileSelf | Static$ True"); - card.setSVar("DBHideawayEffectExileSelf", "DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile"); - final Trigger changeZoneTrigger = TriggerHandler.parseTrigger("Mode$ ChangesZone | ValidCard$ Card.IsRemembered | Origin$ Exile | Destination$ Any | TriggerZone$ Command | Execute$ DBHideawayCleanup | Static$ True", card, intrinsic); + + // when the card with hideaway leaves the battlefield, forget all exiled cards + final Trigger changeZoneTrigger = TriggerHandler.parseTrigger("Mode$ ChangesZone | ValidCard$ Card.Self | Origin$ Battlefield | Destination$ Any | TriggerZone$ Battlefield | Static$ True", card, intrinsic); + String cleanupStr = "DB$ Cleanup | ClearRemembered$ True"; + changeZoneTrigger.setOverridingAbility(AbilityFactory.getAbility(cleanupStr, card)); triggers.add(changeZoneTrigger); - card.setSVar("DBHideawayRemember", "DB$ Animate | Defined$ Imprinted | RememberObjects$ Remembered | Permanent$ True"); - card.setSVar("DBHideawayCleanup", "DB$ Cleanup | ClearRemembered$ True"); for (final Trigger trigger : triggers) { inst.addTrigger(trigger); @@ -2714,25 +2742,31 @@ public class CardFactoryUtil { // Set Madness Triggers final String[] k = keyword.split(":"); final String manacost = k[1]; - final String trigPlay = "TrigPlayMadness" + StringUtils.join(manacost.split(" ")); - final String trigStr = "Mode$ Discarded | ValidCard$ Card.Self | IsMadness$ True | " + - "Execute$ " + trigPlay + " | Secondary$ True | TriggerDescription$ " + - "Play Madness " + ManaCostParser.parse(manacost) + " - " + card.getName(); + final String trigStr = "Mode$ Discarded | ValidCard$ Card.Self | IsMadness$ True | Secondary$ True" + + " | TriggerDescription$ Play Madness " + ManaCostParser.parse(manacost) + " - " + card.getName(); - final String playMadness = "AB$ Play | Cost$ 0 | Defined$ Self | PlayCost$ " + manacost + + final String playMadnessStr = "DB$ Play | Defined$ Self | PlayCost$ " + manacost + " | ConditionDefined$ Self | ConditionPresent$ Card.StrictlySelf+inZoneExile" + - " | Optional$ True | SubAbility$ DBWasNotPlayMadness | RememberPlayed$ True | Madness$ True"; - final String moveToYard = "DB$ ChangeZone | Defined$ Self.StrictlySelf | Origin$ Exile | " + - "Destination$ Graveyard | TrackDiscarded$ True | ConditionDefined$ Remembered | ConditionPresent$" + - " Card | ConditionCompare$ EQ0 | SubAbility$ DBMadnessCleanup"; - final String cleanUp = "DB$ Cleanup | ClearRemembered$ True"; + " | Optional$ True | RememberPlayed$ True | Madness$ True"; + + final String moveToYardStr = "DB$ ChangeZone | Defined$ Self.StrictlySelf | Origin$ Exile" + + " | Destination$ Graveyard | TrackDiscarded$ True | ConditionDefined$ Remembered | ConditionPresent$ Card" + + " Card | ConditionCompare$ EQ0"; + + final String cleanUpStr = "DB$ Cleanup | ClearRemembered$ True"; final Trigger parsedTrigger = TriggerHandler.parseTrigger(trigStr, card, intrinsic); - card.setSVar(trigPlay, playMadness); - card.setSVar("DBWasNotPlayMadness", moveToYard); - card.setSVar("DBMadnessCleanup", cleanUp); + SpellAbility playSA = AbilityFactory.getAbility(playMadnessStr, card); + AbilitySub moveSA = (AbilitySub)AbilityFactory.getAbility(moveToYardStr, card); + AbilitySub cleanupSA = (AbilitySub)AbilityFactory.getAbility(cleanUpStr, card); + moveSA.setSubAbility(cleanupSA); + playSA.setSubAbility(moveSA); + playSA.setIntrinsic(intrinsic); + + parsedTrigger.setOverridingAbility(playSA); + inst.addTrigger(parsedTrigger); } else if (keyword.equals("Melee")) { final String trigStr = "Mode$ Attacks | ValidCard$ Card.Self | Secondary$ True " + @@ -2806,28 +2840,41 @@ public class CardFactoryUtil { inst.addTrigger(trigger); } else if (keyword.equals("Myriad")) { - final String actualTrigger = "Mode$ Attacks | ValidCard$ Card.Self | Execute$ " - + "MyriadAbility | Secondary$ True | TriggerDescription$ Myriad (" - + inst.getReminderText() + ")"; - final String abString = "DB$ RepeatEach | RepeatPlayers$ OpponentsOtherThanDefendingPlayer" - + " | RepeatSubAbility$ MyriadCopy | SubAbility$ MyriadDelTrig"; - final String dbString1 = "DB$ CopyPermanent | Defined$ Self | Tapped$ True | " - + "Optional$ True | CopyAttacking$ Remembered | ChoosePlayerOrPlaneswalker$" - + " True | ImprintCopied$ True"; - final String dbString2 = "DB$ DelayedTrigger | Mode$ Phase | Phase$ EndCombat | " - + "Execute$ MyriadExile | RememberObjects$ Imprinted | TriggerDescription$" - + " Exile the tokens at end of combat. | SubAbility$ MyriadCleanup"; - final String dbString3 = "DB$ ChangeZone | Defined$ DelayTriggerRemembered | Origin$" - + " Battlefield | Destination$ Exile"; - final String dbString4 = "DB$ Cleanup | ClearImprinted$ True"; + final String actualTrigger = "Mode$ Attacks | ValidCard$ Card.Self | Secondary$ True" + + " | TriggerDescription$ Myriad (" + inst.getReminderText() + ")"; + + final String repeatStr = "DB$ RepeatEach | RepeatPlayers$ OpponentsOtherThanDefendingPlayer | ChangeZoneTable$ True"; + + final String copyStr = "DB$ CopyPermanent | Defined$ Self | Tapped$ True | Optional$ True | TokenAttacking$ Remembered" + + " | ChoosePlayerOrPlaneswalker$ True | ImprintTokens$ True"; + + final String delTrigStr = "DB$ DelayedTrigger | Mode$ Phase | Phase$ EndCombat | RememberObjects$ Imprinted" + + " | TriggerDescription$ Exile the tokens at end of combat."; + + final String exileStr = "DB$ ChangeZone | Defined$ DelayTriggerRemembered | Origin$ Battlefield | Destination$ Exile"; + + final String cleanupStr = "DB$ Cleanup | ClearImprinted$ True"; + + SpellAbility repeatSA = AbilityFactory.getAbility(repeatStr, card); + + AbilitySub copySA = (AbilitySub) AbilityFactory.getAbility(copyStr, card); + repeatSA.setAdditionalAbility("RepeatSubAbility", copySA); + + AbilitySub delTrigSA = (AbilitySub) AbilityFactory.getAbility(delTrigStr, card); + + AbilitySub exileSA = (AbilitySub) AbilityFactory.getAbility(exileStr, card); + delTrigSA.setAdditionalAbility("Execute", exileSA); + + AbilitySub cleanupSA = (AbilitySub) AbilityFactory.getAbility(cleanupStr, card); + delTrigSA.setSubAbility(cleanupSA); + + repeatSA.setSubAbility(delTrigSA); + final Trigger parsedTrigger = TriggerHandler.parseTrigger(actualTrigger, card, intrinsic); - card.setSVar("MyriadAbility", abString); - card.setSVar("MyriadCopy", dbString1); - card.setSVar("MyriadDelTrig", dbString2); - card.setSVar("MyriadExile", dbString3); - card.setSVar("MyriadCleanup", dbString4); + repeatSA.setIntrinsic(intrinsic); + parsedTrigger.setOverridingAbility(repeatSA); inst.addTrigger(parsedTrigger); } else if (keyword.startsWith("Partner:")) { // Partner With @@ -2871,17 +2918,18 @@ public class CardFactoryUtil { final String[] k = keyword.split(":"); card.addIntrinsicKeyword("Kicker:Reveal<1/" + k[1] + ">:Generic"); } else if (keyword.equals("Provoke")) { - final String actualTrigger = "Mode$ Attacks | ValidCard$ Card.Self | " - + "OptionalDecider$ You | Execute$ ProvokeAbility | Secondary$ True | TriggerDescription$ " - + inst.getReminderText(); - final String abString = "DB$ MustBlock | ValidTgts$ Creature.DefenderCtrl | " - + "TgtPrompt$ Select target creature defending player controls | SubAbility$ ProvokeUntap"; - final String dbString = "DB$ Untap | Defined$ Targeted"; + final String actualTrigger = "Mode$ Attacks | ValidCard$ Card.Self | OptionalDecider$ You | Secondary$ True" + + " | TriggerDescription$ Provoke (" + inst.getReminderText() + ")"; + final String blockStr = "DB$ MustBlock | ValidTgts$ Creature.DefenderCtrl | TgtPrompt$ Select target creature defending player controls"; + final String untapStr = "DB$ Untap | Defined$ Targeted"; - card.setSVar("ProvokeAbility", abString); - card.setSVar("ProvokeUntap", dbString); + SpellAbility blockSA = AbilityFactory.getAbility(blockStr, card); + AbilitySub untapSA = (AbilitySub)AbilityFactory.getAbility(untapStr, card); + blockSA.setSubAbility(untapSA); final Trigger parsedTrigger = TriggerHandler.parseTrigger(actualTrigger, card, intrinsic); + blockSA.setIntrinsic(intrinsic); + parsedTrigger.setOverridingAbility(blockSA); inst.addTrigger(parsedTrigger); } else if (keyword.equals("Prowess")) { @@ -2895,10 +2943,6 @@ public class CardFactoryUtil { parsedTrigger.setOverridingAbility(AbilityFactory.getAbility(effect, card)); inst.addTrigger(parsedTrigger); - - if (!card.hasSVar("BuffedBy")) { - card.setSVar("BuffedBy", "Card.nonCreature+nonLand"); // for the AI - } } else if (keyword.startsWith("Rampage")) { final String[] k = keyword.split(":"); final String n = k[1]; @@ -2935,24 +2979,30 @@ public class CardFactoryUtil { inst.addTrigger(parsedTrigger); } else if (keyword.startsWith("Recover")) { final String recoverCost = keyword.split(":")[1]; - final String abStr = "DB$ ChangeZone | Defined$ Self" + final String changeStr = "DB$ ChangeZone | Defined$ Self" + " | Origin$ Graveyard | Destination$ Hand | UnlessCost$ " + recoverCost + " | UnlessPayer$ You | UnlessSwitched$ True" - + " | UnlessResolveSubs$ WhenNotPaid | SubAbility$ RecoverExile"; - card.setSVar("RecoverTrig", abStr); - card.setSVar("RecoverExile", "DB$ ChangeZone | Defined$ Self" - + " | Origin$ Graveyard | Destination$ Exile"); + + " | UnlessResolveSubs$ WhenNotPaid"; + final String exileStr = "DB$ ChangeZone | Defined$ Self | Origin$ Graveyard | Destination$ Exile"; + + SpellAbility changeSA = AbilityFactory.getAbility(changeStr, card); + AbilitySub exileSA = (AbilitySub) AbilityFactory.getAbility(exileStr, card); + changeSA.setSubAbility(exileSA); + String trigObject = card.isCreature() ? "Creature.Other+YouOwn" : "Creature.YouOwn"; String trigArticle = card.isCreature() ? "another" : "a"; String trigStr = "Mode$ ChangesZone | ValidCard$ " + trigObject - + " | Origin$ Battlefield | Destination$ Graveyard | " - + "TriggerZones$ Graveyard | Execute$ RecoverTrig | " - + "TriggerDescription$ When " + trigArticle + " creature is " - + "put into your graveyard from the battlefield, you " - + "may pay " + recoverCost + ". If you do, return " - + "CARDNAME from your graveyard to your hand. Otherwise," - + " exile CARDNAME. | Secondary$ True"; + + " | Origin$ Battlefield | Destination$ Graveyard | " + + "TriggerZones$ Graveyard | Secondary$ True | " + + "TriggerDescription$ Recover " + recoverCost + " (When " + trigArticle + " creature is " + + "put into your graveyard from the battlefield, you " + + "may pay " + recoverCost + ". If you do, return " + + "CARDNAME from your graveyard to your hand. Otherwise," + + " exile CARDNAME.)"; final Trigger myTrigger = TriggerHandler.parseTrigger(trigStr, card, intrinsic); + changeSA.setIntrinsic(intrinsic); + myTrigger.setOverridingAbility(changeSA); + inst.addTrigger(myTrigger); } else if (keyword.startsWith("Replicate")) { final String trigScript = "Mode$ SpellCast | ValidCard$ Card.Self | CheckSVar$ ReplicateAmount | Secondary$ True | TriggerDescription$ Copy CARDNAME for each time you paid its replicate cost"; @@ -3255,7 +3305,6 @@ public class CardFactoryUtil { saExile.setIntrinsic(intrinsic); ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, card, intrinsic); - re.setLayer(ReplacementLayer.Other); re.setOverridingAbility(saExile); @@ -3303,8 +3352,6 @@ public class CardFactoryUtil { ReplacementEffect re = ReplacementHandler.parseReplacement(actualRep, card, intrinsic); - re.setLayer(ReplacementLayer.Other); - re.setOverridingAbility(saReveal); inst.addReplacement(re); @@ -3315,7 +3362,6 @@ public class CardFactoryUtil { if (numCounters.equals("X")) { desc = "Bloodthirst X (This creature enters the battlefield with X +1/+1 counters on it, " + "where X is the damage dealt to your opponents this turn.)"; - card.setSVar("X", "Count$BloodthirstAmount"); } else { desc = "Bloodthirst " + numCounters + " (" + inst.getReminderText() + ")"; } @@ -3323,6 +3369,9 @@ public class CardFactoryUtil { final String etbCounter = "etbCounter:P1P1:" + numCounters + ":Bloodthirst$ True:" + desc; final ReplacementEffect re = makeEtbCounter(etbCounter, card, intrinsic); + if (numCounters.equals("X")) { + re.getOverridingAbility().setSVar("X", "Count$BloodthirstAmount"); + } inst.addReplacement(re); } else if (keyword.startsWith("Buyback")) { @@ -3354,7 +3403,6 @@ public class CardFactoryUtil { saReturn.setIntrinsic(intrinsic); ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, card, intrinsic); - re.setLayer(ReplacementLayer.Other); re.setOverridingAbility(saReturn); @@ -3384,32 +3432,33 @@ public class CardFactoryUtil { inst.addReplacement(re); } else if (keyword.startsWith("Devour")) { - final String[] k = keyword.split(":"); final String magnitude = k[1]; - String abStr = "DB$ ChangeZone | Hidden$ True | Origin$ All | Destination$ Battlefield" - + " | Defined$ ReplacedCard"; - String dbStr = "DB$ Sacrifice | Defined$ You | Amount$ DevourSacX | " - + "References$ DevourSacX | SacValid$ Creature.Other | SacMessage$ another creature (Devour "+ magnitude + ") | " - + "RememberSacrificed$ True | Optional$ True | " - + "Devour$ True | SubAbility$ Devour"+magnitude+"Counters"; - String counterStr = "DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ Devour"+magnitude+"X" - + " | ETB$ True | References$ Devour"+magnitude+"X,DevourSize | SubAbility$ DevourCleanup"; + String sacrificeStr = "DB$ Sacrifice | Defined$ You | Amount$ DevourSacX | " + + "SacValid$ Creature.Other | SacMessage$ another creature (Devour "+ magnitude + ") | " + + "RememberSacrificed$ True | Optional$ True | Devour$ True"; - card.setSVar("DevourETB", abStr); - card.setSVar("Devour"+magnitude+"Sac", dbStr); - card.setSVar("DevourSacX", "Count$Valid Creature.YouCtrl+Other"); - card.setSVar("Devour"+magnitude+"Counters", counterStr); - card.setSVar("Devour"+magnitude+"X", "SVar$DevourSize/Times." + magnitude); - card.setSVar("DevourSize", "Count$RememberedSize"); - card.setSVar("DevourCleanup", "DB$ Cleanup | ClearRemembered$ True | SubAbility$ DevourETB"); + String counterStr = "DB$ PutCounter | ETB$ True | Defined$ Self | CounterType$ P1P1 | CounterNum$ DevourX"; + String cleanupStr = "DB$ Cleanup | ClearRemembered$ True"; - String repeffstr = "Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | ReplaceWith$ Devour"+magnitude+"Sac " - + "| Secondary$ True | Description$ Devour " + magnitude + " ("+ inst.getReminderText() + ")"; + AbilitySub sacrificeSA = (AbilitySub) AbilityFactory.getAbility(sacrificeStr, card); + sacrificeSA.setSVar("DevourSacX", "Count$Valid Creature.YouCtrl+Other"); + + AbilitySub counterSA = (AbilitySub) AbilityFactory.getAbility(counterStr, card); + counterSA.setSVar("DevourX", "SVar$DevourSize/Times." + magnitude); + counterSA.setSVar("DevourSize", "Count$RememberedSize"); + sacrificeSA.setSubAbility(counterSA); + + AbilitySub cleanupSA = (AbilitySub) AbilityFactory.getAbility(cleanupStr, card); + counterSA.setSubAbility(cleanupSA); + + String repeffstr = "Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | Secondary$ True | Description$ Devour " + magnitude + " ("+ inst.getReminderText() + ")"; ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, card, intrinsic); - re.setLayer(ReplacementLayer.Other); + + setupETBReplacementAbility(cleanupSA); + re.setOverridingAbility(sacrificeSA); inst.addReplacement(re); } else if (keyword.startsWith("Fading")) { @@ -3457,7 +3506,6 @@ public class CardFactoryUtil { } ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, card, intrinsic); - re.setLayer(ReplacementLayer.Other); re.setOverridingAbility(saExile); @@ -3493,20 +3541,18 @@ public class CardFactoryUtil { } ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, card, intrinsic); - re.setLayer(ReplacementLayer.Other); re.setOverridingAbility(saExile); inst.addReplacement(re); } else if (keyword.startsWith("Madness")) { // Set Madness Replacement effects - String repeffstr = "Event$ Discard | ActiveZones$ Hand | ValidCard$ Card.Self | " + - "ReplaceWith$ DiscardMadness | Secondary$ True | Description$ " + - "Madness: If you discard this card, discard it into exile."; + String repeffstr = "Event$ Discard | ActiveZones$ Hand | ValidCard$ Card.Self | Secondary$ True " + + " | Description$ Madness: If you discard this card, discard it into exile."; ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, card, intrinsic); - String sVarMadness = "DB$ Discard | Defined$ ReplacedPlayer" + - " | Mode$ Defined | DefinedCards$ ReplacedCard | Madness$ True"; - card.setSVar("DiscardMadness", sVarMadness); + String sVarMadness = "DB$ Discard | Defined$ ReplacedPlayer | Mode$ Defined | DefinedCards$ ReplacedCard | Madness$ True"; + + re.setOverridingAbility(AbilityFactory.getAbility(sVarMadness, card)); inst.addReplacement(re); } else if (keyword.startsWith("Modular")) { @@ -3554,7 +3600,6 @@ public class CardFactoryUtil { } ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, card, intrinsic); - re.setLayer(ReplacementLayer.Other); re.setOverridingAbility(saExile); @@ -3722,7 +3767,6 @@ public class CardFactoryUtil { } ReplacementEffect re = ReplacementHandler.parseReplacement(sb.toString(), card, intrinsic); - re.setLayer(ReplacementLayer.Other); re.setOverridingAbility(sa); @@ -4485,8 +4529,8 @@ public class CardFactoryUtil { String ab = "DB$ Play | Defined$ OriginalHost | WithoutManaCost$ True | CopyCard$ True"; - card.setSVar("CipherTrigger", trig); - card.setSVar("PlayEncoded", ab); + svars.put("CipherTrigger", trig); + svars.put("PlayEncoded", ab); } else if (keyword.startsWith("Dash")) { effect = "Mode$ Continuous | Affected$ Card.Self+dashed | AddKeyword$ Haste"; } else if (keyword.equals("Devoid")) { @@ -4738,7 +4782,6 @@ public class CardFactoryUtil { saExile.setSubAbility(saEffect); ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, card, true); - re.setLayer(ReplacementLayer.Other); re.setOverridingAbility(saExile); card.addReplacementEffect(re); diff --git a/forge-game/src/main/java/forge/game/spellability/AlternativeCost.java b/forge-game/src/main/java/forge/game/spellability/AlternativeCost.java index 41f75125da0..bb8029e89aa 100644 --- a/forge-game/src/main/java/forge/game/spellability/AlternativeCost.java +++ b/forge-game/src/main/java/forge/game/spellability/AlternativeCost.java @@ -9,6 +9,7 @@ public enum AlternativeCost { Escape, Evoke, Flashback, + Madness, Offering, Outlast, // ActivatedAbility Prowl, diff --git a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java index e5ee795a76d..f8cd61a35f1 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java @@ -1185,6 +1185,10 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit return isAlternativeCost(AlternativeCost.Evoke); } + public final boolean isMadness() { + return isAlternativeCost(AlternativeCost.Madness); + } + public final boolean isProwl() { return isAlternativeCost(AlternativeCost.Prowl); } diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java b/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java index 03a54518b75..8b17d3f00c1 100644 --- a/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java +++ b/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java @@ -308,7 +308,7 @@ public final class StaticAbilityContinuous { if (layer == StaticAbilityLayer.ABILITIES && params.containsKey("AddAbility")) { final String[] sVars = params.get("AddAbility").split(" & "); for (int i = 0; i < sVars.length; i++) { - sVars[i] = hostCard.getSVar(sVars[i]); + sVars[i] = AbilityUtils.getSVar(stAb, sVars[i]); } addAbilities = sVars; } @@ -316,7 +316,7 @@ public final class StaticAbilityContinuous { if (layer == StaticAbilityLayer.ABILITIES && params.containsKey("AddReplacementEffects")) { final String[] sVars = params.get("AddReplacementEffects").split(" & "); for (int i = 0; i < sVars.length; i++) { - sVars[i] = hostCard.getSVar(sVars[i]); + sVars[i] = AbilityUtils.getSVar(stAb, sVars[i]); } addReplacements = sVars; } @@ -433,7 +433,7 @@ public final class StaticAbilityContinuous { if (params.containsKey("AddTrigger")) { final String[] sVars = params.get("AddTrigger").split(" & "); for (int i = 0; i < sVars.length; i++) { - sVars[i] = hostCard.getSVar(sVars[i]); + sVars[i] = AbilityUtils.getSVar(stAb, sVars[i]); } addTriggers = sVars; } @@ -441,7 +441,7 @@ public final class StaticAbilityContinuous { if (params.containsKey("AddStaticAbility")) { final String[] sVars = params.get("AddStaticAbility").split(" & "); for (int i = 0; i < sVars.length; i++) { - sVars[i] = hostCard.getSVar(sVars[i]); + sVars[i] = AbilityUtils.getSVar(stAb, sVars[i]); } addStatics = sVars; } @@ -668,7 +668,7 @@ public final class StaticAbilityContinuous { // add SVars if (addSVars != null) { for (final String sVar : addSVars) { - String actualSVar = hostCard.getSVar(sVar); + String actualSVar = AbilityUtils.getSVar(stAb, sVar); String name = sVar; if (actualSVar.startsWith("SVar:")) { actualSVar = actualSVar.split("SVar:")[1]; @@ -757,7 +757,8 @@ public final class StaticAbilityContinuous { // with that the TargetedCard does not need the Svars added to them anymore // but only do it if the trigger doesn't already have a overriding ability if (actualTrigger.hasParam("Execute") && actualTrigger.getOverridingAbility() == null) { - SpellAbility sa = AbilityFactory.getAbility(hostCard, actualTrigger.getParam("Execute")); + String svar = AbilityUtils.getSVar(stAb, actualTrigger.getParam("Execute")); + SpellAbility sa = AbilityFactory.getAbility(svar, hostCard); // set hostcard there so when the card is added to trigger, it doesn't make a copy of it sa.setHostCard(affectedCard); // set OriginalHost to get the owner of this static ability diff --git a/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java b/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java index ca24dc431eb..649d023bdbb 100644 --- a/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java +++ b/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java @@ -161,7 +161,6 @@ public class HumanPlaySpellAbility { // if a player failed to play madness cost, move the card to graveyard Card newCard = game.getAction().moveToGraveyard(c, null); newCard.setMadnessWithoutCast(true); - newCard.setMadness(false); } else if (ability.getHostCard().isBestowed()) { ability.getHostCard().unanimateBestow(); }