Splice: do a total rewrite to make it better for the player and more rules conform

This commit is contained in:
Hanmac
2016-12-21 18:32:50 +00:00
parent 77437bdb50
commit 94681b6675
10 changed files with 118 additions and 74 deletions

View File

@@ -84,7 +84,7 @@ import forge.util.collect.FCollection;
* @version $Id$ * @version $Id$
*/ */
public class ComputerUtil { 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(); game.getStack().freezeStack();
final Card source = sa.getHostCard(); final Card source = sa.getHostCard();
@@ -99,6 +99,10 @@ public class ComputerUtil {
CharmEffect.makeChoices(sa); CharmEffect.makeChoices(sa);
} }
if (source.getType().hasStringType("Arcane") && !source.isCopiedSpell()) {
sa = AbilityUtils.addSpliceEffects(sa);
}
if (sa.hasParam("Bestow")) { if (sa.hasParam("Bestow")) {
sa.getHostCard().animateBestow(); sa.getHostCard().animateBestow();
} }

View File

@@ -12,6 +12,7 @@ import com.google.common.base.Predicate;
import com.google.common.base.Predicates; import com.google.common.base.Predicates;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.ListMultimap; import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap; import com.google.common.collect.Multimap;
import forge.LobbyPlayer; import forge.LobbyPlayer;
@@ -898,4 +899,25 @@ public class PlayerControllerAi extends PlayerController {
} }
return SpellApiToAi.Converter.get(api).chooseCardName(player, sa, faces); return SpellApiToAi.Converter.get(api).chooseCardName(player, sa, faces);
} }
@Override
public List<Card> chooseCardsForSplice(SpellAbility sa, List<Card> cards) {
// sort from best to worst
CardLists.sortByCmcDesc(cards);
List<Card> 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;
}
} }

View File

@@ -20,7 +20,6 @@ package forge.game;
import com.google.common.base.Predicates; import com.google.common.base.Predicates;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import forge.card.MagicColor; import forge.card.MagicColor;
import forge.game.ability.AbilityFactory; import forge.game.ability.AbilityFactory;
@@ -326,79 +325,9 @@ public final class GameActionUtil {
} }
} }
} }
// Splice
final List<SpellAbility> 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; return abilities;
} }
/**
* <p>
* getSpliceAbilities.
* </p>
*
* @param sa
* a SpellAbility.
* @return an ArrayList<SpellAbility>.
* get abilities with all Splice options
*/
private static final List<SpellAbility> getSpliceAbilities(SpellAbility sa) {
List<SpellAbility> newSAs = Lists.newArrayList();
List<SpellAbility> 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<String, String> 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) { private static boolean hasUrzaLands(final Player p) {
final CardCollectionView landsControlled = p.getCardsIn(ZoneType.Battlefield); final CardCollectionView landsControlled = p.getCardsIn(ZoneType.Battlefield);
return Iterables.any(landsControlled, Predicates.and(CardPredicates.isType("Urza's"), CardPredicates.isType("Mine"))) return Iterables.any(landsControlled, Predicates.and(CardPredicates.isType("Urza's"), CardPredicates.isType("Mine")))

View File

@@ -1,7 +1,9 @@
package forge.game.ability; package forge.game.ability;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import forge.card.ColorSet; import forge.card.ColorSet;
import forge.card.MagicColor; import forge.card.MagicColor;
@@ -11,6 +13,7 @@ import forge.card.mana.ManaCostShard;
import forge.game.CardTraitBase; import forge.game.CardTraitBase;
import forge.game.Game; import forge.game.Game;
import forge.game.GameObject; import forge.game.GameObject;
import forge.game.ability.AbilityFactory.AbilityRecordType;
import forge.game.card.*; import forge.game.card.*;
import forge.game.cost.Cost; import forge.game.cost.Cost;
import forge.game.mana.ManaCostBeingPaid; 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<Card>() {
@Override
public boolean apply(Card input) {
return input.hasStartOfKeyword("Splice");
}
});
splices.remove(source);
if (splices.isEmpty()) {
return sa;
}
final List<Card> 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<String, String> 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);
}
} }

View File

@@ -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."), 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"), 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."), 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."), 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."), 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."), STRIVE(KeywordWithCost.class, false, "CARDNAME costs %s more to cast for each target beyond the first."),

View File

@@ -8,6 +8,9 @@ public class KeywordWithCostAndType extends KeywordInstance<KeywordWithCostAndTy
@Override @Override
protected void parse(String details) { protected void parse(String details) {
final String[] k = details.split(":");
type = k[0];
cost = new Cost(k[1], false);
} }
@Override @Override

View File

@@ -222,6 +222,8 @@ public abstract class PlayerController {
public abstract Map<Card, ManaCostShard> chooseCardsForConvoke(SpellAbility sa, ManaCost manaCost, CardCollectionView untappedCreats); public abstract Map<Card, ManaCostShard> chooseCardsForConvoke(SpellAbility sa, ManaCost manaCost, CardCollectionView untappedCreats);
public abstract List<Card> chooseCardsForSplice(SpellAbility sa, List<Card> cards);
public abstract String chooseCardName(SpellAbility sa, Predicate<ICardFace> cpp, String valid, String message); public abstract String chooseCardName(SpellAbility sa, Predicate<ICardFace> cpp, String valid, String message);
public abstract String chooseCardName(SpellAbility sa, List<ICardFace> faces, String message); public abstract String chooseCardName(SpellAbility sa, List<ICardFace> faces, String message);

View File

@@ -7,6 +7,7 @@ import java.util.Map;
import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
import org.testng.collections.Lists;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
@@ -671,4 +672,9 @@ public class PlayerControllerForTests extends PlayerController {
return null; return null;
} }
@Override
public List<Card> chooseCardsForSplice(SpellAbility sa, List<Card> cards) {
return Lists.newArrayList();
}
} }

View File

@@ -87,6 +87,10 @@ public class HumanPlay {
CharmEffect.makeChoices(sa); CharmEffect.makeChoices(sa);
} }
if (source.getType().hasStringType("Arcane")) {
sa = AbilityUtils.addSpliceEffects(sa);
}
if (sa.hasParam("Bestow")) { if (sa.hasParam("Bestow")) {
source.animateBestow(); source.animateBestow();
} }
@@ -173,7 +177,7 @@ public class HumanPlay {
* @param sa * @param sa
* a {@link forge.game.spellability.SpellAbility} object. * 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); FThreads.assertExecutedByEdt(false);
final Card source = sa.getHostCard(); final Card source = sa.getHostCard();
@@ -183,6 +187,9 @@ public class HumanPlay {
if (sa.getApi() == ApiType.Charm && !sa.isWrapper() && !sa.isCopied()) { if (sa.getApi() == ApiType.Charm && !sa.isWrapper() && !sa.isCopied()) {
CharmEffect.makeChoices(sa); CharmEffect.makeChoices(sa);
} }
if (source.getType().hasStringType("Arcane") && !sa.isCopied()) {
sa = AbilityUtils.addSpliceEffects(sa);
}
final CostPayment payment = new CostPayment(sa.getPayCosts(), sa); final CostPayment payment = new CostPayment(sa.getPayCosts(), sa);
final HumanPlaySpellAbility req = new HumanPlaySpellAbility(controller, sa, payment); final HumanPlaySpellAbility req = new HumanPlaySpellAbility(controller, sa, payment);

View File

@@ -1938,4 +1938,10 @@ public class PlayerControllerHuman
ICardFace face = getGui().one(message, faces); ICardFace face = getGui().one(message, faces);
return face == null ? "" : face.getName(); return face == null ? "" : face.getName();
} }
@Override
public List<Card> chooseCardsForSplice(SpellAbility sa, List<Card> cards) {
return getGui().many("Choose cards to Splice onto", "Chosen Cards", 0, cards.size(), cards, sa.getHostCard().getView());
}
} }