mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-18 11:48:02 +00:00
Splice: do a total rewrite to make it better for the player and more rules conform
This commit is contained in:
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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")))
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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."),
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user