From 3644b8f74ff6c9c132d2c204721266549f84a95c Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Sun, 9 Jun 2019 09:22:57 +0000 Subject: [PATCH] MH1 Slice onto instance or sorcery --- .../src/main/java/forge/ai/ComputerUtil.java | 20 ++++++------- .../java/forge/game/ability/AbilityUtils.java | 27 +++++++++++++----- .../src/main/java/forge/game/card/Card.java | 28 +++++++++++++++++-- .../java/forge/game/card/CardFactoryUtil.java | 4 +-- .../java/forge/game/card/token/TokenInfo.java | 4 ++- .../game/keyword/KeywordWithCostAndType.java | 20 ++++++++++++- .../res/cardsfolder/upcoming/everdream.txt | 7 +++++ .../cardsfolder/upcoming/splicers_skill.txt | 7 +++++ forge-gui/res/lists/token-images.txt | 1 + .../src/main/java/forge/player/HumanPlay.java | 8 ++---- .../main/java/forge/util/ImageFetcher.java | 2 +- 11 files changed, 95 insertions(+), 33 deletions(-) create mode 100644 forge-gui/res/cardsfolder/upcoming/everdream.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/splicers_skill.txt diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtil.java b/forge-ai/src/main/java/forge/ai/ComputerUtil.java index e91baca69a3..9d5199a51d2 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtil.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtil.java @@ -76,17 +76,15 @@ public class ComputerUtil { source.setSplitStateToPlayAbility(sa); if (sa.isSpell() && !source.isCopiedSpell()) { - if (source.getType().hasStringType("Arcane")) { - sa = AbilityUtils.addSpliceEffects(sa); - if (sa.getSplicedCards() != null && !sa.getSplicedCards().isEmpty() && ai.getController().isAI()) { - // we need to reconsider and retarget the SA after additional SAs have been added onto it via splice, - // otherwise the AI will fail to add the card to stack and that'll knock it out of the game - sa.resetTargets(); - if (((PlayerControllerAi) ai.getController()).getAi().canPlaySa(sa) != AiPlayDecision.WillPlay) { - // for whatever reason the AI doesn't want to play the thing with the spliced subs anymore, - // proceeding past this point may result in an illegal play - return false; - } + sa = AbilityUtils.addSpliceEffects(sa); + if (sa.getSplicedCards() != null && !sa.getSplicedCards().isEmpty() && ai.getController().isAI()) { + // we need to reconsider and retarget the SA after additional SAs have been added onto it via splice, + // otherwise the AI will fail to add the card to stack and that'll knock it out of the game + sa.resetTargets(); + if (((PlayerControllerAi) ai.getController()).getAi().canPlaySa(sa) != AiPlayDecision.WillPlay) { + // for whatever reason the AI doesn't want to play the thing with the spliced subs anymore, + // proceeding past this point may result in an illegal play + return false; } } 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 fc359308f81..72ac2d57344 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java @@ -17,6 +17,7 @@ import forge.game.GameObject; import forge.game.ability.AbilityFactory.AbilityRecordType; import forge.game.card.*; import forge.game.cost.Cost; +import forge.game.keyword.Keyword; import forge.game.keyword.KeywordInterface; import forge.game.mana.ManaConversionMatrix; import forge.game.mana.ManaCostBeingPaid; @@ -1843,10 +1844,23 @@ public class AbilityUtils { final Card source = sa.getHostCard(); final Player player = sa.getActivatingPlayer(); - final CardCollection splices = CardLists.filter(player.getCardsIn(ZoneType.Hand), new Predicate() { + final CardCollectionView hand = player.getCardsIn(ZoneType.Hand); + + if (hand.isEmpty()) { + return sa; + } + + final CardCollection splices = CardLists.filter(hand, new Predicate() { @Override public boolean apply(Card input) { - return input.hasStartOfKeyword("Splice"); + for (final KeywordInterface inst : input.getKeywords(Keyword.SPLICE)) { + String k = inst.getOriginal(); + final String n[] = k.split(":"); + if (source.isValid(n[1].split(","), player, input, sa)) { + return true; + } + } + return false; } }); @@ -1871,12 +1885,11 @@ public class AbilityUtils { public static void addSpliceEffect(final SpellAbility sa, final Card c) { Cost spliceCost = null; - for (final KeywordInterface inst : c.getKeywords()) { + // This Function thinks that Splice exist only once on the card + for (final KeywordInterface inst : c.getKeywords(Keyword.SPLICE)) { final String k = inst.getOriginal(); - if (k.startsWith("Splice")) { - final String n[] = k.split(":"); - spliceCost = new Cost(n[2], false); - } + final String n[] = k.split(":"); + spliceCost = new Cost(n[2], false); } if (spliceCost == null) diff --git a/forge-game/src/main/java/forge/game/card/Card.java b/forge-game/src/main/java/forge/game/card/Card.java index d0c0de37f92..2c52dc2a270 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -2198,7 +2198,22 @@ public class Card extends GameEntity implements Comparable { } else if (keyword.startsWith("Splice")) { final String[] n = keyword.split(":"); final Cost cost = new Cost(n[2], false); - sbAfter.append("Splice onto ").append(n[1]).append(" ").append(cost.toSimpleString()); + + String desc; + + if (n.length > 3) { + desc = n[3]; + } else { + String k[] = n[1].split(","); + for (int i = 0; i < k.length; i++) { + if (CardType.isACardType(k[i])) { + k[i] = k[i].toLowerCase(); + } + } + desc = StringUtils.join(k, " or "); + } + + sbAfter.append("Splice onto ").append(desc).append(" ").append(cost.toSimpleString()); sbAfter.append(" (" + inst.getReminderText() + ")").append("\r\n"); } else if (keyword.equals("Storm")) { sbAfter.append("Storm ("); @@ -4236,7 +4251,14 @@ public class Card extends GameEntity implements Comparable { return getAmountOfKeyword(k, currentState); } public final int getAmountOfKeyword(final Keyword k, CardState state) { - return state.getCachedKeyword(k).size(); + return getKeywords(k, state).size(); + } + + public final Collection getKeywords(final Keyword k) { + return getKeywords(k, currentState); + } + public final Collection getKeywords(final Keyword k, CardState state) { + return state.getCachedKeyword(k); } // This is for keywords with a number like Bushido, Annihilator and Rampage. @@ -4254,7 +4276,7 @@ public class Card extends GameEntity implements Comparable { */ public final int getKeywordMagnitude(final Keyword k, CardState state) { int count = 0; - for (final KeywordInterface inst : state.getCachedKeyword(k)) { + for (final KeywordInterface inst : getKeywords(k, state)) { String kw = inst.getOriginal(); // this can't be used yet for everything because of X values in Bushido X // KeywordInterface#getAmount diff --git a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java index a507c45575a..ba3755676cd 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java @@ -4435,9 +4435,7 @@ public class CardFactoryUtil { sb.append(" | CostDesc$ ").append(ManaCostParser.parse(manacost)); sb.append("| Origin$ Library | Destination$ Hand |"); sb.append("ChangeType$ ").append(type); - // no Keyword.getReminderText, it does not work yet - sb.append(" | SpellDescription$ (Search your library for a ").append(desc).append(" card, reveal it,"); - sb.append(" and put it into your hand. Then shuffle your library.)"); + sb.append(" | SpellDescription$ (").append(inst.getReminderText()).append(")"); SpellAbility sa = AbilityFactory.getAbility(sb.toString(), card); sa.setIsCycling(true); diff --git a/forge-game/src/main/java/forge/game/card/token/TokenInfo.java b/forge-game/src/main/java/forge/game/card/token/TokenInfo.java index af46b52d072..5d4ff3b79aa 100644 --- a/forge-game/src/main/java/forge/game/card/token/TokenInfo.java +++ b/forge-game/src/main/java/forge/game/card/token/TokenInfo.java @@ -18,6 +18,8 @@ import forge.game.keyword.KeywordInterface; import forge.game.player.Player; import forge.game.spellability.SpellAbility; import forge.item.PaperToken; + +import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import java.util.List; @@ -231,7 +233,7 @@ public class TokenInfo { final Card host = sa.getHostCard(); final Game game = host.getGame(); - String edition = host.getSetCode(); + String edition = ObjectUtils.firstNonNull(sa.getOriginalHost(), host).getSetCode(); PaperToken token = StaticData.instance().getAllTokens().getToken(script, edition); if (token != null) { 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 1fb8b5a318e..b1e988e5bdb 100644 --- a/forge-game/src/main/java/forge/game/keyword/KeywordWithCostAndType.java +++ b/forge-game/src/main/java/forge/game/keyword/KeywordWithCostAndType.java @@ -1,20 +1,38 @@ package forge.game.keyword; +import org.apache.commons.lang3.StringUtils; + +import forge.card.CardType; import forge.game.cost.Cost; public class KeywordWithCostAndType extends KeywordInstance { private Cost cost; private String type; + private String strType = null; @Override protected void parse(String details) { final String[] k = details.split(":"); type = k[0]; cost = new Cost(k[1], false); + if (k.length > 2) { + strType = k[2]; + } else { + String n[] = type.split(","); + for (int i = 0; i < n.length; i++) { + if (CardType.isACardType(n[i])) { + n[i] = n[i].toLowerCase(); + } else if (n[i].equals("Basic")) { + n[i] = "basic land"; + } + } + + strType = StringUtils.join(n, " or "); + } } @Override protected String formatReminderText(String reminderText) { - return String.format(reminderText, cost.toSimpleString(), type); + return String.format(reminderText, cost.toSimpleString(), strType); } } diff --git a/forge-gui/res/cardsfolder/upcoming/everdream.txt b/forge-gui/res/cardsfolder/upcoming/everdream.txt new file mode 100644 index 00000000000..9c20ff575db --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/everdream.txt @@ -0,0 +1,7 @@ +Name:Everdream +ManaCost:1 U +Types:Instant +K:Splice:Instant,Sorcery:2 U +A:SP$ Draw | Cost$ 1 U | NumCards$ 1 | SpellDescription$ Draw a card. +AI:RemoveDeck:Random +Oracle:Draw a card.\nSplice onto instant or sorcery {2}{U} (As you cast an instant or sorcery 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.) diff --git a/forge-gui/res/cardsfolder/upcoming/splicers_skill.txt b/forge-gui/res/cardsfolder/upcoming/splicers_skill.txt new file mode 100644 index 00000000000..5fbc3e08724 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/splicers_skill.txt @@ -0,0 +1,7 @@ +Name:Splicer's Skill +ManaCost:2 W +Types:Sorcery +K:Splice:Instant,Sorcery:3 W +A:SP$ Token | Cost$ 2 W | TokenAmount$ 1 | TokenScript$ c_3_3_a_golem | TokenOwner$ You | LegacyImage$ c 3 3 a golem mh1 | SpellDescription$ Create a 3/3 colorless Golem artifact creature token. +AI:RemoveDeck:Random +Oracle:Create a 3/3 colorless Golem artifact creature token.\nSplice onto instant or sorcery {3}{W} (As you cast an instant or sorcery 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.) diff --git a/forge-gui/res/lists/token-images.txt b/forge-gui/res/lists/token-images.txt index 8e170aa1b6f..2bfd2c93f56 100644 --- a/forge-gui/res/lists/token-images.txt +++ b/forge-gui/res/lists/token-images.txt @@ -325,6 +325,7 @@ c_2_2_pincher_5dn.jpg https://downloads.cardforge.org/images/tokens/c_2_2_pinche c_2_2_a_spawn_dst.jpg https://downloads.cardforge.org/images/tokens/c_2_2_a_spawn_dst.jpg c_2_2_a_spawn_td2.jpg https://downloads.cardforge.org/images/tokens/c_2_2_a_spawn_td2.jpg c_3_2_eldrazi_horror_emn.jpg https://downloads.cardforge.org/images/tokens/c_3_2_eldrazi_horror_emn.jpg +c_3_3_a_golem_mh1.jpg https://downloads.cardforge.org/images/tokens/c_3_3_a_golem_mh1.jpg c_3_3_a_golem_mm2.jpg https://downloads.cardforge.org/images/tokens/c_3_3_a_golem_mm2.jpg c_3_3_a_golem_mm3.jpg https://downloads.cardforge.org/images/tokens/c_3_3_a_golem_mm3.jpg c_3_3_a_golem_nph.jpg https://downloads.cardforge.org/images/tokens/c_3_3_a_golem_nph.jpg diff --git a/forge-gui/src/main/java/forge/player/HumanPlay.java b/forge-gui/src/main/java/forge/player/HumanPlay.java index d55ca8fa3a7..f1899e7c155 100644 --- a/forge-gui/src/main/java/forge/player/HumanPlay.java +++ b/forge-gui/src/main/java/forge/player/HumanPlay.java @@ -85,9 +85,7 @@ public class HumanPlay { CharmEffect.makeChoices(sa); } - if (sa.isSpell() && source.getType().hasStringType("Arcane")) { - sa = AbilityUtils.addSpliceEffects(sa); - } + sa = AbilityUtils.addSpliceEffects(sa); if (sa.hasParam("Bestow")) { source.animateBestow(); @@ -199,9 +197,7 @@ public class HumanPlay { if (sa.getApi() == ApiType.Charm && !sa.isWrapper()) { CharmEffect.makeChoices(sa); } - if (sa.isSpell() && source.getType().hasStringType("Arcane")) { - sa = AbilityUtils.addSpliceEffects(sa); - } + sa = AbilityUtils.addSpliceEffects(sa); } final CostPayment payment = new CostPayment(sa.getPayCosts(), sa); diff --git a/forge-gui/src/main/java/forge/util/ImageFetcher.java b/forge-gui/src/main/java/forge/util/ImageFetcher.java index 9ab7a134387..fa08293340f 100644 --- a/forge-gui/src/main/java/forge/util/ImageFetcher.java +++ b/forge-gui/src/main/java/forge/util/ImageFetcher.java @@ -71,7 +71,7 @@ public abstract class ImageFetcher { final String filename = imageKey.substring(2) + ".jpg"; String tokenUrl = tokenImages.get(filename); if (tokenUrl == null) { - System.err.println("No specified file.. Attempting to download from default Url"); + System.err.println("No specified file for '" + filename + "'.. Attempting to download from default Url"); tokenUrl = String.format("%s%s", ForgeConstants.URL_TOKEN_DOWNLOAD, filename); } destFile = new File(ForgeConstants.CACHE_TOKEN_PICS_DIR, filename);