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$
|
||||
*/
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -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<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.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<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;
|
||||
}
|
||||
|
||||
/**
|
||||
* <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) {
|
||||
final CardCollectionView landsControlled = p.getCardsIn(ZoneType.Battlefield);
|
||||
return Iterables.any(landsControlled, Predicates.and(CardPredicates.isType("Urza's"), CardPredicates.isType("Mine")))
|
||||
|
||||
@@ -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<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."),
|
||||
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."),
|
||||
|
||||
@@ -8,6 +8,9 @@ public class KeywordWithCostAndType extends KeywordInstance<KeywordWithCostAndTy
|
||||
|
||||
@Override
|
||||
protected void parse(String details) {
|
||||
final String[] k = details.split(":");
|
||||
type = k[0];
|
||||
cost = new Cost(k[1], false);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -222,6 +222,8 @@ public abstract class PlayerController {
|
||||
|
||||
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, 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.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<Card> chooseCardsForSplice(SpellAbility sa, List<Card> cards) {
|
||||
return Lists.newArrayList();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -1938,4 +1938,10 @@ public class PlayerControllerHuman
|
||||
ICardFace face = getGui().one(message, faces);
|
||||
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