diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtil.java b/forge-ai/src/main/java/forge/ai/ComputerUtil.java index 0ef0e8eab6d..db8268bbc82 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtil.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtil.java @@ -84,7 +84,7 @@ import forge.util.collect.FCollection; * @version $Id$ */ public class ComputerUtil { - public static boolean handlePlayingSpellAbility(final Player ai, final SpellAbility sa, final Game game) { + public static boolean handlePlayingSpellAbility(final Player ai, SpellAbility sa, final Game game) { game.getStack().freezeStack(); final Card source = sa.getHostCard(); @@ -99,6 +99,10 @@ public class ComputerUtil { CharmEffect.makeChoices(sa); } + if (source.getType().hasStringType("Arcane") && !source.isCopiedSpell()) { + sa = AbilityUtils.addSpliceEffects(sa); + } + if (sa.hasParam("Bestow")) { sa.getHostCard().animateBestow(); } diff --git a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java index 14fec123ce3..bfcba292042 100644 --- a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java +++ b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java @@ -12,6 +12,7 @@ import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.collect.Iterables; import com.google.common.collect.ListMultimap; +import com.google.common.collect.Lists; import com.google.common.collect.Multimap; import forge.LobbyPlayer; @@ -898,4 +899,25 @@ public class PlayerControllerAi extends PlayerController { } return SpellApiToAi.Converter.get(api).chooseCardName(player, sa, faces); } + + @Override + public List chooseCardsForSplice(SpellAbility sa, List cards) { + // sort from best to worst + CardLists.sortByCmcDesc(cards); + + List result = Lists.newArrayList(); + + SpellAbility oldSA = sa; + // TODO maybe add some more Logic into it + for (final Card c : cards) { + SpellAbility newSA = oldSA.copy(); + AbilityUtils.addSpliceEffect(newSA, c); + // check if AI still wants or can play the card with spliced effect + if (AiPlayDecision.WillPlay == getAi().canPlayFromEffectAI((Spell) newSA, false, false)) { + oldSA = newSA; + result.add(c); + } + } + return result; + } } diff --git a/forge-game/src/main/java/forge/game/GameActionUtil.java b/forge-game/src/main/java/forge/game/GameActionUtil.java index 7d54ffaaef6..486be9dcb03 100644 --- a/forge-game/src/main/java/forge/game/GameActionUtil.java +++ b/forge-game/src/main/java/forge/game/GameActionUtil.java @@ -20,7 +20,6 @@ package forge.game; import com.google.common.base.Predicates; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; -import com.google.common.collect.Maps; import forge.card.MagicColor; import forge.game.ability.AbilityFactory; @@ -326,79 +325,9 @@ public final class GameActionUtil { } } } - - // Splice - final List newAbilities = Lists.newArrayList(); - for (SpellAbility sa : abilities) { - if (sa.isSpell() && sa.getHostCard().getType().hasStringType("Arcane") && sa.getApi() != null ) { - newAbilities.addAll(GameActionUtil.getSpliceAbilities(sa)); - } - } - abilities.addAll(newAbilities); return abilities; } - /** - *

- * getSpliceAbilities. - *

- * - * @param sa - * a SpellAbility. - * @return an ArrayList. - * get abilities with all Splice options - */ - private static final List getSpliceAbilities(SpellAbility sa) { - List newSAs = Lists.newArrayList(); - List allSaCombinations = Lists.newArrayList(); - allSaCombinations.add(sa); - Card source = sa.getHostCard(); - - for (Card c : sa.getActivatingPlayer().getCardsIn(ZoneType.Hand)) { - if (c.equals(source)) { - continue; - } - - String spliceKwCost = null; - for (String keyword : c.getKeywords()) { - if (keyword.startsWith("Splice")) { - spliceKwCost = keyword.substring(19); - break; - } - } - - if (spliceKwCost == null) - continue; - - SpellAbility firstSpell = c.getCurrentState().getFirstAbility(); - Map params = Maps.newHashMap(firstSpell.getMapParams()); - AbilityRecordType rc = AbilityRecordType.getRecordType(params); - ApiType api = rc.getApiTypeOf(params); - AbilitySub subAbility = (AbilitySub) AbilityFactory.getAbility(AbilityRecordType.SubAbility, api, params, null, c, null); - - // Add the subability to all existing variants - for (int i = 0; i < allSaCombinations.size(); ++i) { - //create a new spell copy - final SpellAbility newSA = allSaCombinations.get(i).copy(); - newSA.setBasicSpell(false); - newSA.setPayCosts(new Cost(spliceKwCost, false).add(newSA.getPayCosts())); - newSA.setDescription(newSA.getDescription() + " (Splicing " + c + " onto it)"); - newSA.addSplicedCards(c); - - //add the spliced ability to the end of the chain - newSA.appendSubAbility(subAbility); - - newSA.setActivatingPlayer(sa.getActivatingPlayer()); - - newSA.setHostCard(source); - - newSAs.add(newSA); - allSaCombinations.add(++i, newSA); - } - } - return newSAs; - } - private static boolean hasUrzaLands(final Player p) { final CardCollectionView landsControlled = p.getCardsIn(ZoneType.Battlefield); return Iterables.any(landsControlled, Predicates.and(CardPredicates.isType("Urza's"), CardPredicates.isType("Mine"))) diff --git a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java index c77c51b67a9..78a2c7f886f 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java @@ -1,7 +1,9 @@ package forge.game.ability; +import com.google.common.base.Predicate; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; +import com.google.common.collect.Maps; import forge.card.ColorSet; import forge.card.MagicColor; @@ -11,6 +13,7 @@ import forge.card.mana.ManaCostShard; import forge.game.CardTraitBase; import forge.game.Game; import forge.game.GameObject; +import forge.game.ability.AbilityFactory.AbilityRecordType; import forge.game.card.*; import forge.game.cost.Cost; import forge.game.mana.ManaCostBeingPaid; @@ -1720,4 +1723,66 @@ public class AbilityUtils { } } } + + public static SpellAbility addSpliceEffects(final SpellAbility sa) { + final Card source = sa.getHostCard(); + final Player player = sa.getActivatingPlayer(); + + final CardCollection splices = CardLists.filter(player.getCardsIn(ZoneType.Hand), new Predicate() { + @Override + public boolean apply(Card input) { + return input.hasStartOfKeyword("Splice"); + } + }); + + splices.remove(source); + + if (splices.isEmpty()) { + return sa; + } + + final List choosen = player.getController().chooseCardsForSplice(sa, splices); + + if (choosen.isEmpty()) { + return sa; + } + + final SpellAbility newSA = sa.copy(); + for (final Card c : choosen) { + addSpliceEffect(newSA, c); + } + return newSA; + } + + public static void addSpliceEffect(final SpellAbility sa, final Card c) { + Cost spliceCost = null; + for (final String k : c.getKeywords()) { + if (k.startsWith("Splice")) { + final String n[] = k.split(":"); + spliceCost = new Cost(n[2], false); + } + } + + if (spliceCost == null) + return; + + SpellAbility firstSpell = c.getFirstSpellAbility(); + Map params = Maps.newHashMap(firstSpell.getMapParams()); + AbilityRecordType rc = AbilityRecordType.getRecordType(params); + ApiType api = rc.getApiTypeOf(params); + AbilitySub subAbility = (AbilitySub) AbilityFactory.getAbility(AbilityRecordType.SubAbility, api, params, null, c, null); + + subAbility.setActivatingPlayer(sa.getActivatingPlayer()); + subAbility.setHostCard(sa.getHostCard()); + subAbility.setOriginalHost(c); + + //add the spliced ability to the end of the chain + sa.appendSubAbility(subAbility); + + // update master SpellAbility + sa.setBasicSpell(false); + sa.setPayCosts(spliceCost.add(sa.getPayCosts())); + sa.setDescription(sa.getDescription() + " (Splicing " + c + " onto it)"); + sa.addSplicedCards(c); + } } diff --git a/forge-game/src/main/java/forge/game/keyword/Keyword.java b/forge-game/src/main/java/forge/game/keyword/Keyword.java index d31365662db..66e6cadf995 100644 --- a/forge-game/src/main/java/forge/game/keyword/Keyword.java +++ b/forge-game/src/main/java/forge/game/keyword/Keyword.java @@ -113,7 +113,7 @@ public enum Keyword { SCAVENGE(KeywordWithCost.class, false, "%s, Exile this card from your graveyard: Put a number of +1/+1 counters equal to this card's power on target creature. Scavenge only as a sorcery."), SOULBOND(SimpleKeyword.class, true, "You may pair this creature with another unpaired creature when either enters the battlefield. They remain paired for as long as you control both of them"), SOULSHIFT(KeywordWithAmount.class, false, "When this creature dies, you may return target Spirit card with converted mana cost %d or less from your graveyard to your hand."), - SPLICE(KeywordWithCostAndType.class, false, "You may reveal this card from your hand as you cast a %s spell. If you do, copy this card's text box onto that spell and pay %s as an additional cost to cast that spell."), + SPLICE(KeywordWithCostAndType.class, false, "As you cast an %2$s spell, you may reveal this card from your hand and pay its splice cost. If you do, add this card's effects to that spell."), SPLIT_SECOND(SimpleKeyword.class, true, "As long as this spell is on the stack, players can't play other spells or abilities that aren't mana abilities."), STORM(SimpleKeyword.class, false, "When you cast this spell, copy it for each other spell that was cast before it this turn. You may choose new targets for the copies."), STRIVE(KeywordWithCost.class, false, "CARDNAME costs %s more to cast for each target beyond the first."), diff --git a/forge-game/src/main/java/forge/game/keyword/KeywordWithCostAndType.java b/forge-game/src/main/java/forge/game/keyword/KeywordWithCostAndType.java index 46150e83626..d24fb37fe57 100644 --- a/forge-game/src/main/java/forge/game/keyword/KeywordWithCostAndType.java +++ b/forge-game/src/main/java/forge/game/keyword/KeywordWithCostAndType.java @@ -8,6 +8,9 @@ public class KeywordWithCostAndType extends KeywordInstance chooseCardsForConvoke(SpellAbility sa, ManaCost manaCost, CardCollectionView untappedCreats); + public abstract List chooseCardsForSplice(SpellAbility sa, List cards); + public abstract String chooseCardName(SpellAbility sa, Predicate cpp, String valid, String message); public abstract String chooseCardName(SpellAbility sa, List faces, String message); diff --git a/forge-gui-desktop/src/test/java/forge/gamesimulationtests/util/PlayerControllerForTests.java b/forge-gui-desktop/src/test/java/forge/gamesimulationtests/util/PlayerControllerForTests.java index b9541ea4bbd..aa0f6b3525a 100644 --- a/forge-gui-desktop/src/test/java/forge/gamesimulationtests/util/PlayerControllerForTests.java +++ b/forge-gui-desktop/src/test/java/forge/gamesimulationtests/util/PlayerControllerForTests.java @@ -7,6 +7,7 @@ import java.util.Map; import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; +import org.testng.collections.Lists; import com.google.common.base.Predicate; import com.google.common.collect.Iterables; @@ -671,4 +672,9 @@ public class PlayerControllerForTests extends PlayerController { return null; } + @Override + public List chooseCardsForSplice(SpellAbility sa, List cards) { + return Lists.newArrayList(); + } + } diff --git a/forge-gui/src/main/java/forge/player/HumanPlay.java b/forge-gui/src/main/java/forge/player/HumanPlay.java index 8c5614c6de1..6598d9d493e 100644 --- a/forge-gui/src/main/java/forge/player/HumanPlay.java +++ b/forge-gui/src/main/java/forge/player/HumanPlay.java @@ -87,6 +87,10 @@ public class HumanPlay { CharmEffect.makeChoices(sa); } + if (source.getType().hasStringType("Arcane")) { + sa = AbilityUtils.addSpliceEffects(sa); + } + if (sa.hasParam("Bestow")) { source.animateBestow(); } @@ -173,7 +177,7 @@ public class HumanPlay { * @param sa * a {@link forge.game.spellability.SpellAbility} object. */ - public static final void playSaWithoutPayingManaCost(final PlayerControllerHuman controller, final Game game, final SpellAbility sa, boolean mayChooseNewTargets) { + public static final void playSaWithoutPayingManaCost(final PlayerControllerHuman controller, final Game game, SpellAbility sa, boolean mayChooseNewTargets) { FThreads.assertExecutedByEdt(false); final Card source = sa.getHostCard(); @@ -183,6 +187,9 @@ public class HumanPlay { if (sa.getApi() == ApiType.Charm && !sa.isWrapper() && !sa.isCopied()) { CharmEffect.makeChoices(sa); } + if (source.getType().hasStringType("Arcane") && !sa.isCopied()) { + sa = AbilityUtils.addSpliceEffects(sa); + } final CostPayment payment = new CostPayment(sa.getPayCosts(), sa); final HumanPlaySpellAbility req = new HumanPlaySpellAbility(controller, sa, payment); diff --git a/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java b/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java index a2ea7e2c226..5fbfc9d3c0f 100644 --- a/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java +++ b/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java @@ -1938,4 +1938,10 @@ public class PlayerControllerHuman ICardFace face = getGui().one(message, faces); return face == null ? "" : face.getName(); } + + @Override + public List chooseCardsForSplice(SpellAbility sa, List cards) { + return getGui().many("Choose cards to Splice onto", "Chosen Cards", 0, cards.size(), cards, sa.getHostCard().getView()); + } + }