From 0453409490e6466a900bd581bcfa2d6142c6e340 Mon Sep 17 00:00:00 2001 From: Hanmac Date: Sat, 3 Dec 2016 16:59:41 +0000 Subject: [PATCH] SpellAbility: now does store the SubAbilities which are used by some Effects, that makes it working to copy them to other cards. rewrite Entwine to use CardFactoryUtil --- .../main/java/forge/game/GameActionUtil.java | 11 --- .../forge/game/ability/AbilityFactory.java | 65 +++++++------- .../game/ability/effects/AttachEffect.java | 1 - .../game/ability/effects/CharmEffect.java | 36 ++++---- .../ability/effects/ChooseGenericEffect.java | 29 ++---- .../ability/effects/ChooseNumberEffect.java | 44 ++++----- .../game/ability/effects/ClashEffect.java | 30 ++----- .../ability/effects/DelayedTriggerEffect.java | 8 +- .../game/ability/effects/FlipCoinEffect.java | 58 ++++-------- .../ability/effects/MultiplePilesEffect.java | 20 ++--- .../ability/effects/RepeatEachEffect.java | 24 ++--- .../game/ability/effects/RepeatEffect.java | 14 +-- .../game/ability/effects/TwoPilesEffect.java | 22 ++--- .../src/main/java/forge/game/card/Card.java | 3 +- .../java/forge/game/card/CardFactory.java | 13 +++ .../java/forge/game/card/CardFactoryUtil.java | 21 +++++ .../forge/game/spellability/SpellAbility.java | 89 ++++++++++++++++++- .../main/java/forge/game/zone/MagicStack.java | 17 ++-- 18 files changed, 248 insertions(+), 257 deletions(-) diff --git a/forge-game/src/main/java/forge/game/GameActionUtil.java b/forge-game/src/main/java/forge/game/GameActionUtil.java index 996b81b330f..59b8aa46cdb 100644 --- a/forge-game/src/main/java/forge/game/GameActionUtil.java +++ b/forge-game/src/main/java/forge/game/GameActionUtil.java @@ -264,17 +264,6 @@ public final class GameActionUtil { i++; } } - } else if (keyword.startsWith("Entwine")) { - for (int i = 0; i < abilities.size(); i++) { - final SpellAbility newSA = abilities.get(i).copy(); - SpellAbility entwine = AbilityFactory.buildEntwineAbility(newSA); - entwine.setPayCosts(new Cost(keyword.substring(8), false).add(newSA.getPayCosts())); - entwine.addOptionalCost(OptionalCost.Entwine); - if (newSA.canPlay()) { - abilities.add(i, entwine); - i++; - } - } } else if (keyword.startsWith("Kicker")) { String[] sCosts = TextUtil.split(keyword.substring(6), ':'); boolean generic = "Generic".equals(sCosts[sCosts.length - 1]); diff --git a/forge-game/src/main/java/forge/game/ability/AbilityFactory.java b/forge-game/src/main/java/forge/game/ability/AbilityFactory.java index 7763a33821c..12451e738b1 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityFactory.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityFactory.java @@ -28,6 +28,7 @@ import forge.util.FileSection; import java.util.List; import java.util.Map; +import com.google.common.base.Function; import com.google.common.collect.Lists; /** @@ -40,6 +41,15 @@ import com.google.common.collect.Lists; */ public final class AbilityFactory { + static final List additionalAbilityKeys = Lists.newArrayList( + "WinSubAbility", "OtherwiseSubAbility", // Clash + "ChooseNumberSubAbility", "Lowest", "Highest", // ChooseNumber + "HeadsSubAbility", "TailsSubAbility", "LoseSubAbility", // FlipCoin + "ChosenPile", "UnchosenPile", // MultiplePiles & TwoPiles + "RepeatSubAbility", // Repeat & RepeatEach + "Execute" // DelayedTrigger + ); + public enum AbilityRecordType { Ability("AB"), Spell("SP"), @@ -136,7 +146,7 @@ public final class AbilityFactory { return abCost; } - public static final SpellAbility getAbility(AbilityRecordType type, ApiType api, Map mapParams, Cost abCost, Card hostCard) { + public static final SpellAbility getAbility(AbilityRecordType type, ApiType api, Map mapParams, Cost abCost,final Card hostCard) { TargetRestrictions abTgt = mapParams.containsKey("ValidTgts") ? readTarget(mapParams) : null; if (api == ApiType.CopySpellAbility || api == ApiType.Counter || api == ApiType.ChangeTargets || api == ApiType.ControlSpell) { @@ -181,10 +191,6 @@ public final class AbilityFactory { spellAbility.setSVar(mapParams.get("Execute"), hostCard.getSVar(mapParams.get("Execute"))); } - if (api == ApiType.RepeatEach) { - spellAbility.setSVar(mapParams.get("RepeatSubAbility"), hostCard.getSVar(mapParams.get("RepeatSubAbility"))); - } - if (mapParams.containsKey("PreventionSubAbility")) { spellAbility.setSVar(mapParams.get("PreventionSubAbility"), hostCard.getSVar(mapParams.get("PreventionSubAbility"))); } @@ -193,6 +199,25 @@ public final class AbilityFactory { spellAbility.setSubAbility(getSubAbility(hostCard, mapParams.get("SubAbility"))); } + for (final String key : additionalAbilityKeys) { + if (mapParams.containsKey(key)) { + spellAbility.setAdditionalAbility(key, getSubAbility(hostCard, mapParams.get(key))); + } + } + + if (api == ApiType.Charm || api == ApiType.GenericChoice) { + final String key = "Choices"; + if (mapParams.containsKey(key)) { + List names = Lists.newArrayList(mapParams.get(key).split(",")); + spellAbility.setAdditionalAbilityList(key, Lists.transform(names, new Function() { + @Override + public AbilitySub apply(String input) { + return getSubAbility(hostCard, input); + } + })); + } + } + if (spellAbility instanceof SpellApiBased && hostCard.isPermanent()) { spellAbility.setDescription(spellAbility.getHostCard().getName()); } else if (mapParams.containsKey("SpellDescription")) { @@ -211,7 +236,7 @@ public final class AbilityFactory { } else { spellAbility.setDescription(""); } - + initializeParams(spellAbility, mapParams); makeRestrictions(spellAbility, mapParams); makeConditions(spellAbility, mapParams); @@ -395,32 +420,4 @@ public final class AbilityFactory { left.appendSubAbility(right); return left; } - - public static final SpellAbility buildEntwineAbility(final SpellAbility sa) { - final Card source = sa.getHostCard(); - final String[] saChoices = sa.getParam("Choices").split(","); - if (sa.getApi() != ApiType.Charm || saChoices.length != 2) - throw new IllegalStateException("Entwine ability may be built only on charm cards"); - final String ab = source.getSVar(saChoices[0]); - Map firstMap = getMapParams(ab); - AbilityRecordType firstType = AbilityRecordType.getRecordType(firstMap); - ApiType firstApi = firstType.getApiTypeOf(firstMap); - firstMap.put("StackDecription", firstMap.get("SpellDescription")); - firstMap.put("SpellDescription", sa.getDescription() + " Entwine (Choose both if you pay the entwine cost.)"); - SpellAbility entwineSA = getAbility(AbilityRecordType.Spell, firstApi, firstMap, new Cost(sa.getPayCosts().toSimpleString(), false), source); - - final String ab2 = source.getSVar(saChoices[1]); - Map secondMap = getMapParams(ab2); - ApiType secondApi = firstType.getApiTypeOf(secondMap); - secondMap.put("StackDecription", secondMap.get("SpellDescription")); - secondMap.put("SpellDescription", ""); - AbilitySub sub = (AbilitySub) getAbility(AbilityRecordType.SubAbility, secondApi, secondMap, null, source); - entwineSA.appendSubAbility(sub); - - entwineSA.setBasicSpell(false); - entwineSA.setActivatingPlayer(sa.getActivatingPlayer()); - entwineSA.setRestrictions(sa.getRestrictions()); - return entwineSA; - } - } // end class AbilityFactory diff --git a/forge-game/src/main/java/forge/game/ability/effects/AttachEffect.java b/forge-game/src/main/java/forge/game/ability/effects/AttachEffect.java index 3d96ba6dd93..5687b3e8d8f 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/AttachEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/AttachEffect.java @@ -5,7 +5,6 @@ import forge.game.Game; import forge.game.GameEntity; import forge.game.GameObject; import forge.game.ability.AbilityUtils; -import forge.game.ability.ApiType; import forge.game.ability.SpellAbilityEffect; import forge.game.card.Card; import forge.game.card.CardCollection; diff --git a/forge-game/src/main/java/forge/game/ability/effects/CharmEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CharmEffect.java index 5ff3ffb26ae..3a6243ef52b 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/CharmEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/CharmEffect.java @@ -1,7 +1,7 @@ package forge.game.ability.effects; -import com.google.common.collect.Iterables; -import forge.game.ability.AbilityFactory; +import com.google.common.collect.Lists; + import forge.game.ability.SpellAbilityEffect; import forge.game.card.Card; import forge.game.player.Player; @@ -10,7 +10,6 @@ import forge.game.spellability.SpellAbility; import forge.util.Lang; import forge.util.collect.FCollection; -import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; @@ -20,7 +19,6 @@ public class CharmEffect extends SpellAbilityEffect { public static List makePossibleOptions(final SpellAbility sa) { final Card source = sa.getHostCard(); Iterable restriction = null; - final String[] saChoices = sa.getParam("Choices").split(","); if (sa.hasParam("ChoiceRestriction")) { String rest = sa.getParam("ChoiceRestriction"); @@ -28,24 +26,16 @@ public class CharmEffect extends SpellAbilityEffect { restriction = source.getRemembered(); } } - - List choices = new ArrayList(); + int indx = 0; - for (final String saChoice : saChoices) { - if (restriction != null && Iterables.contains(restriction, saChoice)) { - // If there is a choice restriction, and the current choice fails that, skip it. - continue; - } - final String ab = source.getSVar(saChoice); - AbilitySub sub = (AbilitySub) AbilityFactory.getAbility(ab, source); - if (sa.isIntrinsic()) { - sub.setIntrinsic(true); - sub.changeText(); - } + List choices = sa.getAdditionalAbilityList("Choices"); + if (restriction != null) { + choices.removeAll(Lists.newArrayList(restriction)); + } + // set CharmOrder + for (AbilitySub sub : choices) { sub.setTrigger(sa.isTrigger()); - sub.setSVar("CharmOrder", Integer.toString(indx)); - choices.add(sub); indx++; } return choices; @@ -138,6 +128,12 @@ public class CharmEffect extends SpellAbilityEffect { //this resets all previous choices sa.setSubAbility(null); + // Entwine does use all Choices + if (sa.isEntwine()) { + chainAbilities(sa, makePossibleOptions(sa)); + return; + } + final int num = Integer.parseInt(sa.hasParam("CharmNum") ? sa.getParam("CharmNum") : "1"); final int min = sa.hasParam("MinCharmNum") ? Integer.parseInt(sa.getParam("MinCharmNum")) : num; @@ -165,7 +161,7 @@ public class CharmEffect extends SpellAbilityEffect { Collections.sort(chosen, new Comparator() { @Override public int compare(AbilitySub o1, AbilitySub o2) { - return Integer.parseInt(o1.getSVar("CharmOrder")) - Integer.parseInt(o2.getSVar("CharmOrder")); + return Integer.compare(o1.getSVarInt("CharmOrder"), o2.getSVarInt("CharmOrder")); } }); diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChooseGenericEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChooseGenericEffect.java index 5ab83f7873c..b1fe9ab6ebd 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ChooseGenericEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ChooseGenericEffect.java @@ -1,19 +1,18 @@ package forge.game.ability.effects; -import forge.game.ability.AbilityFactory; import forge.game.ability.AbilityUtils; import forge.game.ability.SpellAbilityEffect; import forge.game.card.Card; import forge.game.event.GameEventCardModeChosen; import forge.game.player.Player; -import forge.game.spellability.AbilitySub; import forge.game.spellability.SpellAbility; import forge.game.spellability.TargetRestrictions; import forge.util.MyRandom; -import java.util.ArrayList; import java.util.List; +import com.google.common.collect.Lists; + public class ChooseGenericEffect extends SpellAbilityEffect { @Override @@ -31,12 +30,8 @@ public class ChooseGenericEffect extends SpellAbilityEffect { @Override public void resolve(SpellAbility sa) { final Card host = sa.getHostCard(); - final String[] choices = sa.getParam("Choices").split(","); - final List abilities = new ArrayList(); - - for (String s : choices) { - abilities.add(AbilityFactory.getAbility(host.getSVar(s), host)); - } + + final List abilities = Lists.newArrayList(sa.getAdditionalAbilityList("Choices")); final List tgtPlayers = getDefinedPlayersOrTargeted(sa); final TargetRestrictions tgt = sa.getTargetRestrictions(); @@ -46,24 +41,18 @@ public class ChooseGenericEffect extends SpellAbilityEffect { continue; } - int idxChosen = 0; - String chosenName; + SpellAbility chosenSA = null; if (sa.hasParam("AtRandom")) { - idxChosen = MyRandom.getRandom().nextInt(choices.length); - chosenName = choices[idxChosen]; + int idxChosen = MyRandom.getRandom().nextInt(abilities.size()); + chosenSA = abilities.get(idxChosen); } else { - SpellAbility saChosen = p.getController().chooseSingleSpellForEffect(abilities, sa, "Choose one"); - idxChosen = abilities.indexOf(saChosen); - chosenName = choices[idxChosen]; + chosenSA = p.getController().chooseSingleSpellForEffect(abilities, sa, "Choose one"); } - SpellAbility chosenSA = AbilityFactory.getAbility(host.getSVar(chosenName), host); - String chosenValue = abilities.get(idxChosen).getDescription(); + String chosenValue = chosenSA.getDescription(); if (sa.hasParam("ShowChoice")) { boolean dontNotifySelf = sa.getParam("ShowChoice").equals("ExceptSelf"); p.getGame().getAction().nofityOfValue(sa, p, chosenValue, dontNotifySelf ? sa.getActivatingPlayer() : null); } - chosenSA.setActivatingPlayer(sa.getActivatingPlayer()); - ((AbilitySub) chosenSA).setParent(sa); p.getGame().fireEvent(new GameEventCardModeChosen(p, host.getName(), chosenValue, sa.hasParam("ShowChoice"))); AbilityUtils.resolve(chosenSA); } diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChooseNumberEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChooseNumberEffect.java index 81cf71f0b2a..9a78bec051a 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ChooseNumberEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ChooseNumberEffect.java @@ -1,6 +1,5 @@ package forge.game.ability.effects; -import forge.game.ability.AbilityFactory; import forge.game.ability.AbilityUtils; import forge.game.ability.SpellAbilityEffect; import forge.game.card.Card; @@ -9,11 +8,13 @@ import forge.game.spellability.AbilitySub; import forge.game.spellability.SpellAbility; import forge.game.spellability.TargetRestrictions; -import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; + import java.util.Random; public class ChooseNumberEffect extends SpellAbilityEffect { @@ -49,7 +50,7 @@ public class ChooseNumberEffect extends SpellAbilityEffect { final List tgtPlayers = getTargetPlayers(sa); final TargetRestrictions tgt = sa.getTargetRestrictions(); - final Map chooseMap = new HashMap(); + final Map chooseMap = Maps.newHashMap(); for (final Player p : tgtPlayers) { if ((tgt == null) || p.canBeTargetedBy(sa)) { @@ -80,8 +81,8 @@ public class ChooseNumberEffect extends SpellAbilityEffect { } if (secretlyChoose) { StringBuilder sb = new StringBuilder(); - List highestNum = new ArrayList(); - List lowestNum = new ArrayList(); + List highestNum = Lists.newArrayList(); + List lowestNum = Lists.newArrayList(); int highest = 0; int lowest = Integer.MAX_VALUE; for (Entry ev : chooseMap.entrySet()) { @@ -106,13 +107,8 @@ public class ChooseNumberEffect extends SpellAbilityEffect { } card.getGame().getAction().nofityOfValue(sa, card, sb.toString(), null); if (sa.hasParam("ChooseNumberSubAbility")) { - SpellAbility sub = AbilityFactory.getAbility(card.getSVar(sa.getParam("ChooseNumberSubAbility")), card); - if (sa.isIntrinsic()) { - sub.setIntrinsic(true); - sub.changeText(); - } - sub.setActivatingPlayer(sa.getActivatingPlayer()); - ((AbilitySub) sub).setParent(sa); + AbilitySub sub = sa.getAdditonalAbility("ChooseNumberSubAbility"); + for (Player p : chooseMap.keySet()) { card.addRemembered(p); card.setChosenNumber(chooseMap.get(p)); @@ -122,32 +118,22 @@ public class ChooseNumberEffect extends SpellAbilityEffect { } if (sa.hasParam("Lowest")) { - SpellAbility action = AbilityFactory.getAbility(card.getSVar(sa.getParam("Lowest")), card); - if (sa.isIntrinsic()) { - action.setIntrinsic(true); - action.changeText(); - } - action.setActivatingPlayer(sa.getActivatingPlayer()); - ((AbilitySub) action).setParent(sa); + AbilitySub sub = sa.getAdditonalAbility("Lowest"); + for (Player p : lowestNum) { card.addRemembered(p); card.setChosenNumber(lowest); - AbilityUtils.resolve(action); + AbilityUtils.resolve(sub); card.clearRemembered(); } } if (sa.hasParam("Highest")) { - SpellAbility action = AbilityFactory.getAbility(card.getSVar(sa.getParam("Highest")), card); - if (sa.isIntrinsic()) { - action.setIntrinsic(true); - action.changeText(); - } - action.setActivatingPlayer(sa.getActivatingPlayer()); - ((AbilitySub) action).setParent(sa); + AbilitySub sub = sa.getAdditonalAbility("Highest"); + for (Player p : highestNum) { card.addRemembered(p); card.setChosenNumber(highest); - AbilityUtils.resolve(action); + AbilityUtils.resolve(sub); card.clearRemembered(); } if (sa.hasParam("RememberHighest")) { diff --git a/forge-game/src/main/java/forge/game/ability/effects/ClashEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ClashEffect.java index 6f9448617da..903d5d00bd8 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ClashEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ClashEffect.java @@ -1,7 +1,6 @@ package forge.game.ability.effects; import forge.game.GameAction; -import forge.game.ability.AbilityFactory; import forge.game.ability.AbilityUtils; import forge.game.ability.SpellAbilityEffect; import forge.game.card.Card; @@ -36,32 +35,19 @@ public class ClashEffect extends SpellAbilityEffect { runParams.put("Player", sa.getHostCard().getController()); if (victory) { - if (sa.hasParam("WinSubAbility")) { - final SpellAbility win = AbilityFactory.getAbility( - sa.getHostCard().getSVar(sa.getParam("WinSubAbility")), sa.getHostCard()); - if (sa.isIntrinsic()) { - win.setIntrinsic(true); - win.changeText(); - } - win.setActivatingPlayer(sa.getHostCard().getController()); - ((AbilitySub) win).setParent(sa); - AbilityUtils.resolve(win); + AbilitySub sub = sa.getAdditonalAbility("WinSubAbility"); + if (sub != null) { + AbilityUtils.resolve(sub); } + runParams.put("Won", "True"); } else { - if (sa.hasParam("OtherwiseSubAbility")) { - final SpellAbility otherwise = AbilityFactory.getAbility( - sa.getHostCard().getSVar(sa.getParam("OtherwiseSubAbility")), sa.getHostCard()); - if (sa.isIntrinsic()) { - otherwise.setIntrinsic(true); - otherwise.changeText(); - } - otherwise.setActivatingPlayer(sa.getHostCard().getController()); - ((AbilitySub) otherwise).setParent(sa); - - AbilityUtils.resolve(otherwise); + AbilitySub sub = sa.getAdditonalAbility("OtherwiseSubAbility"); + if (sub != null) { + AbilityUtils.resolve(sub); } + runParams.put("Won", "False"); } diff --git a/forge-game/src/main/java/forge/game/ability/effects/DelayedTriggerEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DelayedTriggerEffect.java index d075acdd861..e2ab5c267a5 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/DelayedTriggerEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/DelayedTriggerEffect.java @@ -1,6 +1,5 @@ package forge.game.ability.effects; -import forge.game.ability.AbilityFactory; import forge.game.ability.AbilityUtils; import forge.game.ability.ApiType; import forge.game.ability.SpellAbilityEffect; @@ -13,6 +12,7 @@ import java.util.HashMap; import java.util.Map; import com.google.common.collect.Iterables; +import com.google.common.collect.Maps; public class DelayedTriggerEffect extends SpellAbilityEffect { @@ -31,9 +31,7 @@ public class DelayedTriggerEffect extends SpellAbilityEffect { @Override public void resolve(SpellAbility sa) { - - Map mapParams = new HashMap(); - sa.copyParamsToMap(mapParams); + Map mapParams = Maps.newHashMap(sa.getMapParams()); if (mapParams.containsKey("Cost")) { mapParams.remove("Cost"); } @@ -75,7 +73,7 @@ public class DelayedTriggerEffect extends SpellAbilityEffect { } if (mapParams.containsKey("Execute")) { - SpellAbility overridingSA = AbilityFactory.getAbility(sa.getSVar(mapParams.get("Execute")), sa.getHostCard()); + SpellAbility overridingSA = sa.getAdditonalAbility("Execute"); overridingSA.setActivatingPlayer(sa.getActivatingPlayer()); // Set Transform timestamp when the delayed trigger is created if (ApiType.SetState == overridingSA.getApi()) { diff --git a/forge-game/src/main/java/forge/game/ability/effects/FlipCoinEffect.java b/forge-game/src/main/java/forge/game/ability/effects/FlipCoinEffect.java index ff3e7018c64..24f927fe1db 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/FlipCoinEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/FlipCoinEffect.java @@ -1,7 +1,6 @@ package forge.game.ability.effects; import forge.game.GameObject; -import forge.game.ability.AbilityFactory; import forge.game.ability.AbilityUtils; import forge.game.ability.SpellAbilityEffect; import forge.game.card.Card; @@ -13,8 +12,10 @@ import forge.game.spellability.SpellAbility; import forge.game.trigger.TriggerType; import forge.util.MyRandom; -import java.util.HashMap; import java.util.List; +import java.util.Map; + +import com.google.common.collect.Maps; public class FlipCoinEffect extends SpellAbilityEffect { @@ -73,28 +74,14 @@ public class FlipCoinEffect extends SpellAbilityEffect { } if (resultIsHeads) { - if (sa.hasParam("HeadsSubAbility")) { - final SpellAbility heads = AbilityFactory.getAbility(host.getSVar(sa.getParam("HeadsSubAbility")), host); - if (sa.isIntrinsic()) { - heads.setIntrinsic(true); - heads.changeText(); - } - heads.setActivatingPlayer(player); - ((AbilitySub) heads).setParent(sa); - - AbilityUtils.resolve(heads); + AbilitySub sub = sa.getAdditonalAbility("HeadsSubAbility"); + if (sub != null) { + AbilityUtils.resolve(sub); } } else { - if (sa.hasParam("TailsSubAbility")) { - final SpellAbility tails = AbilityFactory.getAbility(host.getSVar(sa.getParam("TailsSubAbility")), host); - if (sa.isIntrinsic()) { - tails.setIntrinsic(true); - tails.changeText(); - } - tails.setActivatingPlayer(player); - ((AbilitySub) tails).setParent(sa); - - AbilityUtils.resolve(tails); + AbilitySub sub = sa.getAdditonalAbility("TailsSubAbility"); + if (sub != null) { + AbilityUtils.resolve(sub); } } } else { @@ -102,32 +89,19 @@ public class FlipCoinEffect extends SpellAbilityEffect { if (sa.getParam("RememberWinner") != null) { host.addRemembered(host); } - if (sa.hasParam("WinSubAbility")) { - final SpellAbility win = AbilityFactory.getAbility(host.getSVar(sa.getParam("WinSubAbility")), host); - if (sa.isIntrinsic()) { - win.setIntrinsic(true); - win.changeText(); - } - win.setActivatingPlayer(player); - ((AbilitySub) win).setParent(sa); - - AbilityUtils.resolve(win); + AbilitySub sub = sa.getAdditonalAbility("WinSubAbility"); + if (sub != null) { + AbilityUtils.resolve(sub); } // runParams.put("Won","True"); } else { if (sa.getParam("RememberLoser") != null) { host.addRemembered(host); } - if (sa.hasParam("LoseSubAbility")) { - final SpellAbility lose = AbilityFactory.getAbility(host.getSVar(sa.getParam("LoseSubAbility")), host); - if (sa.isIntrinsic()) { - lose.setIntrinsic(true); - lose.changeText(); - } - lose.setActivatingPlayer(player); - ((AbilitySub) lose).setParent(sa); - AbilityUtils.resolve(lose); + AbilitySub sub = sa.getAdditonalAbility("LoseSubAbility"); + if (sub != null) { + AbilityUtils.resolve(sub); } // runParams.put("Won","False"); } @@ -186,7 +160,7 @@ public class FlipCoinEffect extends SpellAbilityEffect { caller.getGame().getAction().nofityOfValue(sa, caller, result ? "win" : "lose", null); // Run triggers - HashMap runParams = new HashMap(); + Map runParams = Maps.newHashMap(); runParams.put("Player", caller); runParams.put("Result", Boolean.valueOf(result)); caller.getGame().getTriggerHandler().runTrigger(TriggerType.FlippedCoin, runParams, false); diff --git a/forge-game/src/main/java/forge/game/ability/effects/MultiplePilesEffect.java b/forge-game/src/main/java/forge/game/ability/effects/MultiplePilesEffect.java index ba10a50e6b8..58c217fadea 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/MultiplePilesEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/MultiplePilesEffect.java @@ -1,8 +1,9 @@ package forge.game.ability.effects; import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; -import forge.game.ability.AbilityFactory; import forge.game.ability.AbilityUtils; import forge.game.ability.SpellAbilityEffect; import forge.game.card.Card; @@ -16,8 +17,6 @@ import forge.game.spellability.TargetRestrictions; import forge.game.zone.ZoneType; import forge.util.Aggregates; -import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -57,7 +56,7 @@ public class MultiplePilesEffect extends SpellAbilityEffect { final ZoneType zone = sa.hasParam("Zone") ? ZoneType.smartValueOf(sa.getParam("Zone")) : ZoneType.Battlefield; final boolean randomChosen = sa.hasParam("RandomChosen"); final int piles = Integer.parseInt(sa.getParam("Piles")); - final Map> record = new HashMap>(); + final Map> record = Maps.newHashMap(); String valid = ""; if (sa.hasParam("ValidCards")) { @@ -83,7 +82,7 @@ public class MultiplePilesEffect extends SpellAbilityEffect { } pool = CardLists.getValidCards(pool, valid, source.getController(), source); - List pileList = new ArrayList(); + List pileList = Lists.newArrayList(); for (int i = 1; i < piles; i++) { int size = pool.size(); @@ -104,14 +103,11 @@ public class MultiplePilesEffect extends SpellAbilityEffect { source.addRemembered(c); } } - final SpellAbility action = AbilityFactory.getAbility(source.getSVar(sa.getParam("ChosenPile")), source); - if (sa.isIntrinsic()) { - action.setIntrinsic(true); - action.changeText(); + + AbilitySub sub = sa.getAdditonalAbility("ChosenPile"); + if (sub != null) { + AbilityUtils.resolve(sub); } - action.setActivatingPlayer(sa.getActivatingPlayer()); - ((AbilitySub) action).setParent(sa); - AbilityUtils.resolve(action); source.clearRemembered(); } } diff --git a/forge-game/src/main/java/forge/game/ability/effects/RepeatEachEffect.java b/forge-game/src/main/java/forge/game/ability/effects/RepeatEachEffect.java index 73482a5bd4c..21a96e6c8d8 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/RepeatEachEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/RepeatEachEffect.java @@ -1,17 +1,13 @@ package forge.game.ability.effects; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; -import java.util.Set; import com.google.common.collect.Lists; +import com.google.common.collect.Maps; import forge.game.Game; -import forge.game.ability.AbilityFactory; import forge.game.ability.AbilityUtils; import forge.game.ability.SpellAbilityEffect; import forge.game.card.Card; @@ -35,16 +31,7 @@ public class RepeatEachEffect extends SpellAbilityEffect { public void resolve(SpellAbility sa) { Card source = sa.getHostCard(); - // setup subability to repeat - final SpellAbility repeat = AbilityFactory.getAbility(sa.getSVar(sa.getParam("RepeatSubAbility")), source); - - if (sa.isIntrinsic()) { - repeat.setIntrinsic(true); - repeat.changeText(); - } - - repeat.setActivatingPlayer(sa.getActivatingPlayer()); - ((AbilitySub) repeat).setParent(sa); + AbilitySub repeat = sa.getAdditonalAbility("RepeatSubAbility"); final Player player = sa.getActivatingPlayer(); final Game game = player.getGame(); @@ -55,7 +42,7 @@ public class RepeatEachEffect extends SpellAbilityEffect { CardCollectionView repeatCards = null; if (sa.hasParam("RepeatCards")) { - List zone = new ArrayList(); + List zone = Lists.newArrayList(); if (sa.hasParam("Zone")) { zone = ZoneType.listValueOf(sa.getParam("Zone")); } else { @@ -137,8 +124,7 @@ public class RepeatEachEffect extends SpellAbilityEffect { if (target == null) { target = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa).get(0); } - Set types = new HashSet(target.getCounters().keySet()); - for (CounterType type : types) { + for (CounterType type : target.getCounters().keySet()) { StringBuilder sb = new StringBuilder(); sb.append("Number$").append(target.getCounters(type)); source.setSVar("RepeatSVarCounter", type.getName().toUpperCase()); @@ -148,7 +134,7 @@ public class RepeatEachEffect extends SpellAbilityEffect { } if (recordChoice) { boolean random = sa.hasParam("Random"); - Map> recordMap = new HashMap>(); + Map> recordMap = Maps.newHashMap(); if (sa.hasParam("ChoosePlayer")) { for (Card card : repeatCards) { Player p; diff --git a/forge-game/src/main/java/forge/game/ability/effects/RepeatEffect.java b/forge-game/src/main/java/forge/game/ability/effects/RepeatEffect.java index ebdf2605194..193752ee979 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/RepeatEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/RepeatEffect.java @@ -1,7 +1,6 @@ package forge.game.ability.effects; import forge.game.Game; -import forge.game.ability.AbilityFactory; import forge.game.ability.AbilityUtils; import forge.game.ability.SpellAbilityEffect; import forge.game.card.Card; @@ -26,15 +25,7 @@ public class RepeatEffect extends SpellAbilityEffect { Card source = sa.getHostCard(); // setup subability to repeat - final SpellAbility repeat = AbilityFactory.getAbility(sa.getHostCard().getSVar(sa.getParam("RepeatSubAbility")), source); - - if (sa.isIntrinsic()) { - repeat.setIntrinsic(true); - repeat.changeText(); - } - - repeat.setActivatingPlayer(sa.getActivatingPlayer()); - ((AbilitySub) repeat).setParent(sa); + AbilitySub repeat = sa.getAdditonalAbility("RepeatSubAbility"); Integer maxRepeat = null; if (sa.hasParam("MaxRepeat")) { @@ -50,11 +41,14 @@ public class RepeatEffect extends SpellAbilityEffect { if (maxRepeat != null && maxRepeat <= count) { // TODO Replace Infinite Loop Break with a game draw. Here are the scenarios that can cause this: // Helm of Obedience vs Graveyard to Library replacement effect + + if(source.getName().equals("Helm of Obedience")) { StringBuilder infLoop = new StringBuilder(sa.getHostCard().toString()); infLoop.append(" - To avoid an infinite loop, this repeat has been broken "); infLoop.append(" and the game will now continue in the current state, ending the loop early. "); infLoop.append("Once Draws are available this probably should change to a Draw."); System.out.println(infLoop.toString()); + } break; } } while (checkRepeatConditions(sa)); diff --git a/forge-game/src/main/java/forge/game/ability/effects/TwoPilesEffect.java b/forge-game/src/main/java/forge/game/ability/effects/TwoPilesEffect.java index 2af67023335..38c9883d234 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/TwoPilesEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/TwoPilesEffect.java @@ -1,6 +1,5 @@ package forge.game.ability.effects; -import forge.game.ability.AbilityFactory; import forge.game.ability.AbilityUtils; import forge.game.ability.SpellAbilityEffect; import forge.game.card.Card; @@ -123,14 +122,11 @@ public class TwoPilesEffect extends SpellAbilityEffect { for (final Card z : chosenPile) { card.addRemembered(z); } - final SpellAbility action = AbilityFactory.getAbility(card.getSVar(sa.getParam("ChosenPile")), card); - if (sa.isIntrinsic()) { - action.setIntrinsic(true); - action.changeText(); + + AbilitySub sub = sa.getAdditonalAbility("ChosenPile"); + if (sub != null) { + AbilityUtils.resolve(sub); } - action.setActivatingPlayer(sa.getActivatingPlayer()); - ((AbilitySub) action).setParent(sa); - AbilityUtils.resolve(action); } // take action on the unchosen pile @@ -140,14 +136,10 @@ public class TwoPilesEffect extends SpellAbilityEffect { card.addRemembered(z); } - final SpellAbility action = AbilityFactory.getAbility(card.getSVar(sa.getParam("UnchosenPile")), card); - if (sa.isIntrinsic()) { - action.setIntrinsic(true); - action.changeText(); + AbilitySub sub = sa.getAdditonalAbility("UnchosenPile"); + if (sub != null) { + AbilityUtils.resolve(sub); } - action.setActivatingPlayer(sa.getActivatingPlayer()); - ((AbilitySub) action).setParent(sa); - AbilityUtils.resolve(action); } } } 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 12a32d1d821..ba641bd2508 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -1846,7 +1846,8 @@ public class Card extends GameEntity implements Comparable { sb.append(" (You may pay an additional cost as you cast CARDNAME. If you do, put CARDNAME back into your hand as it resolves.)"); sb.append("\r\n"); } else if (keyword.startsWith("Entwine")) { - final Cost cost = new Cost(keyword.substring(8), false); + final String[] n = keyword.split(":"); + final Cost cost = new Cost(n[1], false); sb.append("Entwine ").append(cost.toSimpleString()); sb.append(" (Choose both if you pay the entwine cost.)"); sb.append("\r\n"); 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 a393a492e4b..61673820f8c 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactory.java +++ b/forge-game/src/main/java/forge/game/card/CardFactory.java @@ -18,8 +18,10 @@ package forge.game.card; import java.util.List; +import java.util.Map; import java.util.Map.Entry; +import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; @@ -652,6 +654,17 @@ public class CardFactory { if (from.getSubAbility() != null) { to.setSubAbility(from.getSubAbility().getCopy()); } + for (Map.Entry e : from.getAdditonalAbilities().entrySet()) { + to.setAdditionalAbility(e.getKey(), e.getValue().getCopy()); + } + for (Map.Entry> e : from.getAdditionalAbilityLists().entrySet()) { + to.setAdditionalAbilityList(e.getKey(), Lists.transform(e.getValue(), new Function() { + @Override + public AbilitySub apply(AbilitySub input) { + return input.getCopy(); + } + })); + } if (from.getRestrictions() != null) { to.setRestrictions((SpellAbilityRestriction) from.getRestrictions().copy()); } 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 6e1833da284..8096c89aba5 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java @@ -2301,6 +2301,9 @@ public class CardFactoryUtil { else if (keyword.startsWith("Emerge")) { addSpellAbility(keyword, card, null); } + else if (keyword.startsWith("Entwine")) { + addSpellAbility(keyword, card, null); + } else if (keyword.startsWith("Escalate")) { addStaticAbility(keyword, card, null); } @@ -3547,6 +3550,24 @@ public class CardFactoryUtil { kws.addSpellAbility(newSA); } card.addSpellAbility(newSA); + } else if (keyword.startsWith("Entwine")) { + final String[] kw = keyword.split(":"); + String costStr = kw[1]; + final SpellAbility sa = card.getFirstSpellAbility(); + + final SpellAbility newSA = sa.copy(); + newSA.getMapParams().put("Secondary", "True"); + newSA.setBasicSpell(false); + newSA.setPayCosts(new Cost(costStr, false).add(sa.getPayCosts())); + newSA.addOptionalCost(OptionalCost.Entwine); + newSA.setDescription(sa.getDescription() + " (Entwine)"); + newSA.setStackDescription(""); // Empty StackDescription to rebuild it. + if (!intrinsic) { + newSA.setTemporary(true); + newSA.setIntrinsic(false); + kws.addSpellAbility(newSA); + } + card.addSpellAbility(newSA); } else if (keyword.equals("Epic")) { // Epic does modify existing SA, and does not add new one 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 10a764cea75..fb8f6f89bdc 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java @@ -17,6 +17,7 @@ */ package forge.game.spellability; +import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; @@ -115,6 +116,9 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit private SpellAbilityRestriction restrictions = new SpellAbilityRestriction(); private SpellAbilityCondition conditions = new SpellAbilityCondition(); private AbilitySub subAbility = null; + + private Map additionalAbilities = Maps.newHashMap(); + private Map> additionalAbilityLists = Maps.newHashMap(); protected ApiType api = null; @@ -204,6 +208,14 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit if (subAbility != null) { subAbility.setHostCard(c); } + for (AbilitySub sa : additionalAbilities.values()) { + sa.setHostCard(c); + } + for (List list : additionalAbilityLists.values()) { + for (AbilitySub sa : list) { + sa.setHostCard(c); + } + } view.updateHostCard(this); view.updateDescription(this); //description can change if host card does @@ -310,6 +322,14 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit if (subAbility != null) { subAbility.setActivatingPlayer(player); } + for (AbilitySub sa : additionalAbilities.values()) { + sa.setActivatingPlayer(player); + } + for (List list : additionalAbilityLists.values()) { + for (AbilitySub sa : list) { + sa.setActivatingPlayer(player); + } + } view.updateCanPlay(this); } @@ -371,10 +391,6 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit return mapParams.containsKey(key); } - public void copyParamsToMap(Map mapParam) { - mapParam.putAll(mapParams); - } - // If this is not null, then ability was made in a factory public ApiType getApi() { return api; @@ -483,6 +499,10 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit return isOptionalCostPaid(OptionalCost.Surge); } + public boolean isEntwine() { + return isOptionalCostPaid(OptionalCost.Entwine); + } + public boolean isOptionalCostPaid(OptionalCost cost) { SpellAbility saRoot = getRootAbility(); return saRoot.optionalCosts.contains(cost); @@ -622,6 +642,50 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit } view.updateDescription(this); //description changes when sub-abilities change } + + public Map getAdditonalAbilities() { + return additionalAbilities; + } + public AbilitySub getAdditonalAbility(final String name) { + if (additionalAbilities.containsKey(name)) { + return additionalAbilities.get(name); + } + return null; + } + + public void setAdditionalAbility(final String name, final AbilitySub sa) { + if (sa == null) { + additionalAbilities.remove(name); + } else { + sa.setParent(this); + additionalAbilities.put(name, sa); + } + view.updateDescription(this); //description changes when sub-abilities change + } + + public Map> getAdditionalAbilityLists() { + return additionalAbilityLists; + } + public List getAdditionalAbilityList(final String name) { + if (additionalAbilityLists.containsKey(name)) { + return additionalAbilityLists.get(name); + } else { + return ImmutableList.of(); + } + } + + public void setAdditionalAbilityList(final String name, final List list) { + if (list == null || list.isEmpty()) { + additionalAbilityLists.remove(name); + } else { + List result = Lists.newArrayList(list); + for (AbilitySub sa : result) { + sa.setParent(this); + } + additionalAbilityLists.put(name, result); + } + view.updateDescription(this); + } public void appendSubAbility(final AbilitySub toAdd) { SpellAbility tailend = this; while (tailend.getSubAbility() != null) { @@ -1450,6 +1514,15 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit if (subAbility != null) { subAbility.changeText(); } + for (AbilitySub sa : additionalAbilities.values()) { + sa.changeText(); + } + + for (List list : additionalAbilityLists.values()) { + for (AbilitySub sa : list) { + sa.changeText(); + } + } } @Override @@ -1458,6 +1531,14 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit if (subAbility != null) { subAbility.setIntrinsic(i); } + for (AbilitySub sa : additionalAbilities.values()) { + sa.setIntrinsic(i); + } + for (List list : additionalAbilityLists.values()) { + for (AbilitySub sa : list) { + sa.setIntrinsic(i); + } + } } public SpellAbilityView getView() { 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 4d995b5cb42..b38902f0b55 100644 --- a/forge-game/src/main/java/forge/game/zone/MagicStack.java +++ b/forge-game/src/main/java/forge/game/zone/MagicStack.java @@ -244,12 +244,9 @@ public class MagicStack /* extends MyObservable */ implements Iterable numTargets || (tgt.getMaxTargets(source, sa) < numTargets)) { - return false; - } + if (!sa.isTargetNumberValid()) { + return false; } return hasLegalTargeting(sa.getSubAbility(), source); }