From 1b47a81b8ce1ec2b8e2a83e1e4d53a15660211d6 Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Mon, 29 Apr 2019 10:03:06 +0000 Subject: [PATCH] ReplacementEffect: rewrite for Riot and Spark Double --- forge-game/src/main/java/forge/game/Game.java | 2 +- .../src/main/java/forge/game/GameAction.java | 28 ------ .../game/ability/effects/CloneEffect.java | 12 +++ .../forge/game/replacement/ReplaceMoved.java | 48 ++-------- .../game/replacement/ReplacementEffect.java | 36 +++++--- .../game/replacement/ReplacementHandler.java | 92 ++++++++++++++----- .../forge/game/spellability/SpellAbility.java | 9 +- .../game/staticability/StaticAbility.java | 2 +- .../ai/simulation/GameSimulatorTest.java | 31 +++++++ .../res/cardsfolder/upcoming/spark_double.txt | 7 ++ 10 files changed, 156 insertions(+), 111 deletions(-) create mode 100644 forge-gui/res/cardsfolder/upcoming/spark_double.txt diff --git a/forge-game/src/main/java/forge/game/Game.java b/forge-game/src/main/java/forge/game/Game.java index d565428ca6b..4e58a1aba55 100644 --- a/forge-game/src/main/java/forge/game/Game.java +++ b/forge-game/src/main/java/forge/game/Game.java @@ -466,7 +466,7 @@ public class Game { } public Zone getZoneOf(final Card card) { - return card.getZone(); + return card.getLastKnownZone(); } public synchronized CardCollectionView getCardsIn(final ZoneType zone) { diff --git a/forge-game/src/main/java/forge/game/GameAction.java b/forge-game/src/main/java/forge/game/GameAction.java index 6f0701f7e16..8db68f5fea1 100644 --- a/forge-game/src/main/java/forge/game/GameAction.java +++ b/forge-game/src/main/java/forge/game/GameAction.java @@ -29,7 +29,6 @@ import forge.game.ability.effects.AttachEffect; import forge.game.card.*; import forge.game.event.*; import forge.game.keyword.KeywordInterface; -import forge.game.keyword.KeywordsChange; import forge.game.player.GameLossReason; import forge.game.player.Player; import forge.game.replacement.ReplacementEffect; @@ -289,33 +288,6 @@ public class GameAction { copied.getOwner().addInboundToken(copied); } - if (toBattlefield) { - // HACK for making the RIOT enchantment look into the Future - // need to check the Keywords what it would have on the Battlefield - Card riotLKI = CardUtil.getLKICopy(copied); - riotLKI.setLastKnownZone(zoneTo); - CardCollection preList = new CardCollection(riotLKI); - checkStaticAbilities(false, Sets.newHashSet(riotLKI), preList); - - List changedTimeStamps = Lists.newArrayList(); - for(Map.Entry e : riotLKI.getChangedCardKeywords().entrySet()) { - if (!copied.hasChangedCardKeywords(e.getKey())) { - KeywordsChange o = e.getValue(); - o.setHostCard(copied); - for (KeywordInterface k : o.getKeywords()) { - for (ReplacementEffect re : k.getReplacements()) { - // this param need to be set, otherwise in ReplaceMoved it fails - re.getMapParams().put("BypassEtbCheck", "True"); - } - } - copied.addChangedCardKeywordsInternal(o, e.getKey()); - changedTimeStamps.add(e.getKey()); - } - } - - checkStaticAbilities(false); - } - Map repParams = Maps.newHashMap(); repParams.put("Event", "Moved"); repParams.put("Affected", copied); diff --git a/forge-game/src/main/java/forge/game/ability/effects/CloneEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CloneEffect.java index dfde4a019ad..91782681f93 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/CloneEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/CloneEffect.java @@ -10,8 +10,11 @@ import forge.game.player.Player; import forge.game.spellability.SpellAbility; import forge.game.zone.ZoneType; +import java.util.Arrays; import java.util.List; +import com.google.common.collect.Lists; + public class CloneEffect extends SpellAbilityEffect { // TODO update this method @@ -48,6 +51,11 @@ public class CloneEffect extends SpellAbilityEffect { final Player activator = sa.getActivatingPlayer(); Card tgtCard = host; final Game game = activator.getGame(); + final List pumpKeywords = Lists.newArrayList(); + + if (sa.hasParam("PumpKeywords")) { + pumpKeywords.addAll(Arrays.asList(sa.getParam("PumpKeywords").split(" & "))); + } // find cloning source i.e. thing to be copied Card cardToCopy = null; @@ -115,6 +123,10 @@ public class CloneEffect extends SpellAbilityEffect { tgtCard.setTapped(true); } + if (!pumpKeywords.isEmpty()) { + tgtCard.addChangedCardKeywords(pumpKeywords, Lists.newArrayList(), false, false, ts); + } + tgtCard.updateStateForView(); //Clear Remembered and Imprint lists diff --git a/forge-game/src/main/java/forge/game/replacement/ReplaceMoved.java b/forge-game/src/main/java/forge/game/replacement/ReplaceMoved.java index d784653eee1..162d87bca99 100644 --- a/forge-game/src/main/java/forge/game/replacement/ReplaceMoved.java +++ b/forge-game/src/main/java/forge/game/replacement/ReplaceMoved.java @@ -1,16 +1,13 @@ package forge.game.replacement; import forge.game.card.Card; -import forge.game.card.CardCollection; -import forge.game.card.CardUtil; + import forge.game.player.Player; import forge.game.spellability.SpellAbility; import forge.game.zone.ZoneType; import java.util.Map; -import com.google.common.collect.Sets; - /** * TODO: Write javadoc for this type. * @@ -50,56 +47,23 @@ public class ReplaceMoved extends ReplacementEffect { } } - boolean matchedZone = false; if (hasParam("Origin")) { - for(ZoneType z : ZoneType.listValueOf(getParam("Origin"))) { - if(z == (ZoneType) runParams.get("Origin")) - matchedZone = true; - } - - if(!matchedZone) - { + ZoneType zt = (ZoneType) runParams.get("Origin"); + if (!ZoneType.listValueOf(getParam("Origin")).contains(zt)) { return false; } } if (hasParam("Destination")) { - matchedZone = false; ZoneType zt = (ZoneType) runParams.get("Destination"); - for(ZoneType z : ZoneType.listValueOf(getParam("Destination"))) { - if(z == zt) - matchedZone = true; - } - - if(!matchedZone) - { + if (!ZoneType.listValueOf(getParam("Destination")).contains(zt)) { return false; } - - if (zt.equals(ZoneType.Battlefield) && getHostCard().equals(affected) && !hasParam("BypassEtbCheck")) { - // would be an etb replacement effect that enters the battlefield - Card lki = CardUtil.getLKICopy(affected); - lki.setLastKnownZone(lki.getController().getZone(zt)); - - CardCollection preList = new CardCollection(lki); - getHostCard().getGame().getAction().checkStaticAbilities(false, Sets.newHashSet(lki), preList); - - // check if when entering the battlefield would still has this RE or is suppressed - if (!lki.hasReplacementEffect(this) || lki.getReplacementEffect(getId()).isSuppressed()) { - return false; - } - } } if (hasParam("ExcludeDestination")) { - matchedZone = false; - for(ZoneType z : ZoneType.listValueOf(getParam("ExcludeDestination"))) { - if(z == (ZoneType) runParams.get("Destination")) - matchedZone = true; - } - - if(matchedZone) - { + ZoneType zt = (ZoneType) runParams.get("Destination"); + if (ZoneType.listValueOf(getParam("ExcludeDestination")).contains(zt)) { return false; } } diff --git a/forge-game/src/main/java/forge/game/replacement/ReplacementEffect.java b/forge-game/src/main/java/forge/game/replacement/ReplacementEffect.java index 0614d3f4bc3..34d1f58538e 100644 --- a/forge-game/src/main/java/forge/game/replacement/ReplacementEffect.java +++ b/forge-game/src/main/java/forge/game/replacement/ReplacementEffect.java @@ -6,12 +6,12 @@ * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ @@ -30,7 +30,7 @@ import java.util.Map; /** * TODO: Write javadoc for this type. - * + * */ public abstract class ReplacementEffect extends TriggerReplacementBase { private static int maxId = 0; @@ -44,6 +44,8 @@ public abstract class ReplacementEffect extends TriggerReplacementBase { /** The has run. */ private boolean hasRun = false; + private List otherChoices = null; + /** * Gets the id. * @@ -66,7 +68,7 @@ public abstract class ReplacementEffect extends TriggerReplacementBase { } /** * Checks for run. - * + * * @return the hasRun */ public final boolean hasRun() { @@ -75,7 +77,7 @@ public abstract class ReplacementEffect extends TriggerReplacementBase { /** * Instantiates a new replacement effect. - * + * * @param map * the map * @param host @@ -94,7 +96,7 @@ public abstract class ReplacementEffect extends TriggerReplacementBase { /** * Sets the checks for run. - * + * * @param hasRun * the hasRun to set */ @@ -102,9 +104,16 @@ public abstract class ReplacementEffect extends TriggerReplacementBase { this.hasRun = hasRun; } + public List getOtherChoices() { + return otherChoices; + } + public void setOtherChoices(List choices) { + this.otherChoices = choices; + } + /** * Can replace. - * + * * @param runParams * the run params * @return true, if successful @@ -115,14 +124,14 @@ public abstract class ReplacementEffect extends TriggerReplacementBase { *

* requirementsCheck. *

- * @param game - * + * @param game + * * @return a boolean. */ public boolean requirementsCheck(Game game) { return this.requirementsCheck(game, this.getMapParams()); } - + public boolean requirementsCheck(Game game, Map params) { if (this.isSuppressed()) { @@ -154,7 +163,7 @@ public abstract class ReplacementEffect extends TriggerReplacementBase { /** * Gets the copy. - * + * * @return the copy */ public final ReplacementEffect copy(final Card host, final boolean lki) { @@ -174,8 +183,9 @@ public abstract class ReplacementEffect extends TriggerReplacementBase { if (!lki) { res.setId(nextId()); res.setHasRun(false); + res.setOtherChoices(null); } - + res.setHostCard(host); res.setActiveZone(validHostZones); @@ -186,7 +196,7 @@ public abstract class ReplacementEffect extends TriggerReplacementBase { /** * Sets the replacing objects. - * + * * @param runParams * the run params * @param spellAbility diff --git a/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java b/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java index f08e0b660a7..736cbecb59a 100644 --- a/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java +++ b/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java @@ -6,12 +6,12 @@ * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ @@ -23,6 +23,8 @@ import forge.game.GameLogEntryType; import forge.game.ability.AbilityFactory; import forge.game.ability.AbilityUtils; import forge.game.card.Card; +import forge.game.card.CardCollection; +import forge.game.card.CardUtil; import forge.game.player.Player; import forge.game.spellability.SpellAbility; import forge.game.zone.Zone; @@ -34,6 +36,7 @@ import forge.util.Visitor; import org.apache.commons.lang3.StringUtils; import com.google.common.collect.Lists; +import com.google.common.collect.Sets; import java.util.*; @@ -74,6 +77,34 @@ public class ReplacementHandler { } public List getReplacementList(final Map runParams, final ReplacementLayer layer) { + + final CardCollection preList = new CardCollection(); + boolean checkAgain = false; + Card affectedLKI = null; + Card affectedCard = null; + + if ("Moved".equals(runParams.get("Event")) && ZoneType.Battlefield.equals(runParams.get("Destination"))) { + // if it was caused by an replacement effect, use the already calculated RE list + // otherwise the RIOT card would cause a StackError + SpellAbility cause = (SpellAbility) runParams.get("Cause"); + if (cause != null && cause.isReplacementAbility()) { + final ReplacementEffect re = cause.getReplacementEffect(); + // only return for same layer + if (layer.equals(re.getLayer())) { + return re.getOtherChoices(); + } + } + + // Rule 614.12 Enter the Battlefield Replacement Effects look at what the card would be on the battlefield + affectedCard = (Card) runParams.get("Affected"); + affectedLKI = CardUtil.getLKICopy(affectedCard); + affectedLKI.setLastKnownZone(affectedCard.getController().getZone(ZoneType.Battlefield)); + preList.add(affectedLKI); + game.getAction().checkStaticAbilities(false, Sets.newHashSet(affectedLKI), preList); + checkAgain = true; + runParams.put("Affected", affectedLKI); + } + final List possibleReplacers = Lists.newArrayList(); // Round up Non-static replacement effects ("Until EOT," or // "The next time you would..." etc) @@ -87,17 +118,19 @@ public class ReplacementHandler { game.forEachCardInGame(new Visitor() { @Override public boolean visit(Card crd) { - for (final ReplacementEffect replacementEffect : crd.getReplacementEffects()) { + final Card c = preList.get(crd); + + for (final ReplacementEffect replacementEffect : c.getReplacementEffects()) { // Use "CheckLKIZone" parameter to test for effects that care abut where the card was last (e.g. Kalitas, Traitor of Ghet // getting hit by mass removal should still produce tokens). - Zone cardZone = "True".equals(replacementEffect.getMapParams().get("CheckSelfLKIZone")) ? game.getChangeZoneLKIInfo(crd).getLastKnownZone() : game.getZoneOf(crd); + Zone cardZone = "True".equals(replacementEffect.getParam("CheckSelfLKIZone")) ? game.getChangeZoneLKIInfo(c).getLastKnownZone() : game.getZoneOf(c); // Replacement effects that are tied to keywords (e.g. damage prevention effects - if the keyword is removed, the replacement // effect should be inactive) if (replacementEffect.hasParam("TiedToKeyword")) { String kw = replacementEffect.getParam("TiedToKeyword"); - if (!crd.hasKeyword(kw)) { + if (!c.hasKeyword(kw)) { continue; } } @@ -113,15 +146,28 @@ public class ReplacementHandler { } return true; } - + }); + + if (checkAgain) { + if (affectedLKI != null && affectedCard != null) { + // need to set the Host Card there so it is not connected to LKI anymore? + // need to be done after canReplace check + for (final ReplacementEffect re : affectedLKI.getReplacementEffects()) { + re.setHostCard(affectedCard); + } + runParams.put("Affected", affectedCard); + } + game.getAction().checkStaticAbilities(false); + } + return possibleReplacers; } - + /** - * + * * Runs any applicable replacement effects. - * + * * @param runParams * the run params,same as for triggers. * @return true if the event was replaced. @@ -138,15 +184,18 @@ public class ReplacementHandler { possibleReplacers.remove(chosenRE); chosenRE.setHasRun(true); - ReplacementResult res = this.executeReplacement(runParams, chosenRE, decider, game); + chosenRE.setOtherChoices(possibleReplacers); + ReplacementResult res = executeReplacement(runParams, chosenRE, decider, game); if (res == ReplacementResult.NotReplaced) { if (!possibleReplacers.isEmpty()) { res = run(runParams); } chosenRE.setHasRun(false); + chosenRE.setOtherChoices(null); return res; } chosenRE.setHasRun(false); + chosenRE.setOtherChoices(null); String message = chosenRE.toString(); if ( !StringUtils.isEmpty(message)) if (chosenRE.getHostCard() != null) { @@ -157,9 +206,9 @@ public class ReplacementHandler { } /** - * + * * Runs a single replacement effect. - * + * * @param replacementEffect * the replacement effect to run */ @@ -179,7 +228,7 @@ public class ReplacementHandler { if (mapParams.containsKey("ReplaceWith")) { final String effectSVar = mapParams.get("ReplaceWith"); final String effectAbString = host.getSVar(effectSVar); - // TODO: the source of replacement effect should be the source of the original effect + // TODO: the source of replacement effect should be the source of the original effect effectSA = AbilityFactory.getAbility(effectAbString, host); //effectSA.setTrigger(true); @@ -210,7 +259,6 @@ public class ReplacementHandler { effectSA.setIntrinsic(true); effectSA.changeText(); } - effectSA.setReplacementAbility(true); effectSA.setReplacementEffect(replacementEffect); } @@ -267,10 +315,10 @@ public class ReplacementHandler { } /** - * + * * Creates an instance of the proper replacement effect object based on raw * script. - * + * * @param repParse * A raw line of script * @param host @@ -278,16 +326,18 @@ public class ReplacementHandler { * @return A finished instance */ public static ReplacementEffect parseReplacement(final String repParse, final Card host, final boolean intrinsic) { - - final Map mapParams = FileSection.parseToMap(repParse, "$", "|"); - return ReplacementHandler.parseReplacement(mapParams, host, intrinsic); + return ReplacementHandler.parseReplacement(parseParams(repParse), host, intrinsic); + } + + public static Map parseParams(final String repParse) { + return FileSection.parseToMap(repParse, "$", "|"); } /** - * + * * Creates an instance of the proper replacement effect object based on a * parsed script. - * + * * @param mapParams * The parsed script * @param host 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 0b3cfa7717b..7d4fe9b8656 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java @@ -91,7 +91,6 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit private boolean trigger = false; private Trigger triggerObj = null; private boolean optionalTrigger = false; - private boolean replacementAbility = false; private ReplacementEffect replacementEffect = null; private int sourceTrigger = -1; private List triggerRemembered = Lists.newArrayList(); @@ -952,13 +951,13 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit } public boolean isReplacementAbility() { - return replacementAbility; - } - public void setReplacementAbility(boolean replacement) { - replacementAbility = replacement; + return getParent() != null ? getParent().isReplacementAbility() : replacementEffect != null; } public ReplacementEffect getReplacementEffect() { + if (getParent() != null) { + return getParent().getReplacementEffect(); + } return replacementEffect; } diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbility.java b/forge-game/src/main/java/forge/game/staticability/StaticAbility.java index bf1cd3984c9..c9b65c667b5 100644 --- a/forge-game/src/main/java/forge/game/staticability/StaticAbility.java +++ b/forge-game/src/main/java/forge/game/staticability/StaticAbility.java @@ -540,7 +540,7 @@ public class StaticAbility extends CardTraitBase implements Comparable