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
This commit is contained in:
Hanmac
2016-12-03 16:59:41 +00:00
parent 5eaa9aca53
commit 0453409490
18 changed files with 248 additions and 257 deletions

View File

@@ -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]);

View File

@@ -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<String> 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<String, String> mapParams, Cost abCost, Card hostCard) {
public static final SpellAbility getAbility(AbilityRecordType type, ApiType api, Map<String, String> 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<String> names = Lists.newArrayList(mapParams.get(key).split(","));
spellAbility.setAdditionalAbilityList(key, Lists.transform(names, new Function<String, AbilitySub>() {
@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<String, String> 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<String, String> 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

View File

@@ -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;

View File

@@ -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<AbilitySub> makePossibleOptions(final SpellAbility sa) {
final Card source = sa.getHostCard();
Iterable<Object> 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<AbilitySub> choices = new ArrayList<AbilitySub>();
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<AbilitySub> 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<AbilitySub>() {
@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"));
}
});

View File

@@ -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<SpellAbility> abilities = new ArrayList<SpellAbility>();
for (String s : choices) {
abilities.add(AbilityFactory.getAbility(host.getSVar(s), host));
}
final List<SpellAbility> abilities = Lists.<SpellAbility>newArrayList(sa.getAdditionalAbilityList("Choices"));
final List<Player> 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);
}

View File

@@ -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<Player> tgtPlayers = getTargetPlayers(sa);
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Map<Player, Integer> chooseMap = new HashMap<Player, Integer>();
final Map<Player, Integer> 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<Player> highestNum = new ArrayList<Player>();
List<Player> lowestNum = new ArrayList<Player>();
List<Player> highestNum = Lists.newArrayList();
List<Player> lowestNum = Lists.newArrayList();
int highest = 0;
int lowest = Integer.MAX_VALUE;
for (Entry<Player, Integer> 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")) {

View File

@@ -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");
}

View File

@@ -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<String, String> mapParams = new HashMap<String, String>();
sa.copyParamsToMap(mapParams);
Map<String, String> 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()) {

View File

@@ -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<String,Object> runParams = new HashMap<String,Object>();
Map<String,Object> runParams = Maps.newHashMap();
runParams.put("Player", caller);
runParams.put("Result", Boolean.valueOf(result));
caller.getGame().getTriggerHandler().runTrigger(TriggerType.FlippedCoin, runParams, false);

View File

@@ -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<Player, List<CardCollectionView>> record = new HashMap<Player, List<CardCollectionView>>();
final Map<Player, List<CardCollectionView>> 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<CardCollectionView> pileList = new ArrayList<CardCollectionView>();
List<CardCollectionView> 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();
}
}

View File

@@ -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<ZoneType> zone = new ArrayList<ZoneType>();
List<ZoneType> 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<CounterType> types = new HashSet<CounterType>(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<Player, List<Card>> recordMap = new HashMap<Player, List<Card>>();
Map<Player, List<Card>> recordMap = Maps.newHashMap();
if (sa.hasParam("ChoosePlayer")) {
for (Card card : repeatCards) {
Player p;

View File

@@ -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));

View File

@@ -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);
}
}
}

View File

@@ -1846,7 +1846,8 @@ public class Card extends GameEntity implements Comparable<Card> {
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");

View File

@@ -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<String, AbilitySub> e : from.getAdditonalAbilities().entrySet()) {
to.setAdditionalAbility(e.getKey(), e.getValue().getCopy());
}
for (Map.Entry<String, List<AbilitySub>> e : from.getAdditionalAbilityLists().entrySet()) {
to.setAdditionalAbilityList(e.getKey(), Lists.transform(e.getValue(), new Function<AbilitySub, AbilitySub>() {
@Override
public AbilitySub apply(AbilitySub input) {
return input.getCopy();
}
}));
}
if (from.getRestrictions() != null) {
to.setRestrictions((SpellAbilityRestriction) from.getRestrictions().copy());
}

View File

@@ -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

View File

@@ -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<String, AbilitySub> additionalAbilities = Maps.newHashMap();
private Map<String, List<AbilitySub>> 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<AbilitySub> 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<AbilitySub> 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<String, String> 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<String, AbilitySub> 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<String, List<AbilitySub>> getAdditionalAbilityLists() {
return additionalAbilityLists;
}
public List<AbilitySub> getAdditionalAbilityList(final String name) {
if (additionalAbilityLists.containsKey(name)) {
return additionalAbilityLists.get(name);
} else {
return ImmutableList.of();
}
}
public void setAdditionalAbilityList(final String name, final List<AbilitySub> list) {
if (list == null || list.isEmpty()) {
additionalAbilityLists.remove(name);
} else {
List<AbilitySub> 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<AbilitySub> 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<AbilitySub> list : additionalAbilityLists.values()) {
for (AbilitySub sa : list) {
sa.setIntrinsic(i);
}
}
}
public SpellAbilityView getView() {

View File

@@ -244,12 +244,9 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
}
}
if (sp.getApi() == ApiType.Charm) {
boolean remember = sp.hasParam("RememberChoice");
if (remember) {
// Remember the ChoiceName here for later handling
source.addRemembered(sp.getSubAbility().getParam("ChoiceName"));
}
if (sp.getApi() == ApiType.Charm && sp.hasParam("RememberChoice")) {
// Remember the Choice here for later handling
source.addRemembered(sp.getSubAbility());
}
//cancel auto-pass for all opponents of activating player
@@ -618,12 +615,8 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
if (sa == null) {
return true;
}
TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
int numTargets = sa.getTargets().getNumTargeted();
if (tgt.getMinTargets(source, sa) > numTargets || (tgt.getMaxTargets(source, sa) < numTargets)) {
return false;
}
if (!sa.isTargetNumberValid()) {
return false;
}
return hasLegalTargeting(sa.getSubAbility(), source);
}