From f401c3900c923d4abeae25a840541a8e3be44b3f Mon Sep 17 00:00:00 2001 From: Adam Pantel <> Date: Wed, 8 Jan 2020 16:38:51 -0500 Subject: [PATCH] Arcane Savant and friends --- .../main/java/forge/ai/AiCostDecision.java | 2 +- .../src/main/java/forge/game/GameAction.java | 3 +- .../src/main/java/forge/game/GameType.java | 5 ++++ .../java/forge/game/ability/AbilityUtils.java | 2 ++ .../ability/effects/ChooseCardNameEffect.java | 19 ++++++++---- .../game/ability/effects/PumpEffect.java | 10 ++++++- .../src/main/java/forge/game/card/Card.java | 2 +- .../java/forge/game/card/CardProperty.java | 11 +++++++ .../src/main/java/forge/game/cost/Cost.java | 8 +++++ .../main/java/forge/game/cost/CostReveal.java | 28 ++++++++++++++--- .../main/java/forge/game/player/Player.java | 30 +++++++++++++++++++ .../spellability/SpellAbilityCondition.java | 24 +++++++++++---- .../spellability/SpellAbilityRestriction.java | 11 +++++++ .../spellability/SpellAbilityVariables.java | 25 ++++++++++++++++ forge-gui/res/cardsfolder/a/arcane_savant.txt | 13 ++++++++ .../cardsfolder/c/caller_of_the_untamed.txt | 13 ++++++++ .../res/cardsfolder/v/volatile_chimera.txt | 16 ++++++++++ .../java/forge/player/HumanCostDecision.java | 7 +++-- .../src/main/java/forge/player/HumanPlay.java | 3 +- 19 files changed, 210 insertions(+), 22 deletions(-) create mode 100644 forge-gui/res/cardsfolder/a/arcane_savant.txt create mode 100644 forge-gui/res/cardsfolder/c/caller_of_the_untamed.txt create mode 100644 forge-gui/res/cardsfolder/v/volatile_chimera.txt diff --git a/forge-ai/src/main/java/forge/ai/AiCostDecision.java b/forge-ai/src/main/java/forge/ai/AiCostDecision.java index 47dfbbd4d4b..aa6f83699ca 100644 --- a/forge-ai/src/main/java/forge/ai/AiCostDecision.java +++ b/forge-ai/src/main/java/forge/ai/AiCostDecision.java @@ -579,7 +579,7 @@ public class AiCostDecision extends CostDecisionMakerBase { @Override public PaymentDecision visit(CostReveal cost) { final String type = cost.getType(); - CardCollectionView hand = player.getCardsIn(ZoneType.Hand); + CardCollectionView hand = player.getCardsIn(cost.getRevealFrom()); if (cost.payCostFromSource()) { if (!hand.contains(source)) { diff --git a/forge-game/src/main/java/forge/game/GameAction.java b/forge-game/src/main/java/forge/game/GameAction.java index 6131e81cbf5..871984d3a4a 100644 --- a/forge-game/src/main/java/forge/game/GameAction.java +++ b/forge-game/src/main/java/forge/game/GameAction.java @@ -1558,8 +1558,7 @@ public class GameAction { // Where there are none, it should bring up speed controls game.fireEvent(new GameEventGameStarted(gameType, first, game.getPlayers())); - // Emissary's Plot - // runPreOpeningHandActions(first); + runPreOpeningHandActions(first); game.setAge(GameStage.Mulligan); for (final Player p1 : game.getPlayers()) { diff --git a/forge-game/src/main/java/forge/game/GameType.java b/forge-game/src/main/java/forge/game/GameType.java index 7ef81786c26..2e8290a03d4 100644 --- a/forge-game/src/main/java/forge/game/GameType.java +++ b/forge-game/src/main/java/forge/game/GameType.java @@ -1,5 +1,6 @@ package forge.game; +import com.google.common.base.Enums; import com.google.common.base.Function; import forge.StaticData; import forge.deck.CardPool; @@ -144,4 +145,8 @@ public enum GameType { public String getDescription() { return description; } + + public static GameType smartValueOf(String name) { + return Enums.getIfPresent(GameType.class, name).orNull(); + } } 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 7a304c73418..9be750c10e7 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java @@ -260,6 +260,8 @@ public class AbilityUtils { list = sa.getRootAbility().getPaidList("SacrificedCards"); } else if (defined.startsWith("Sacrificed")) { list = sa.getRootAbility().getPaidList("Sacrificed"); + } else if (defined.startsWith("Revealed")) { + list = sa.getRootAbility().getPaidList("Revealed"); } else if (defined.startsWith("DiscardedCards")) { list = sa.getRootAbility().getPaidList("DiscardedCards"); } else if (defined.startsWith("Discarded")) { diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChooseCardNameEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChooseCardNameEffect.java index 9e2f06a8958..ab27a6b40cd 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ChooseCardNameEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ChooseCardNameEffect.java @@ -58,6 +58,15 @@ public class ChooseCardNameEffect extends SpellAbilityEffect { validDesc = sa.getParam("ValidDesc"); } + String message; + if (sa.hasParam("SelectPrompt")) { + message = sa.getParam("SelectPrompt"); + } else if (validDesc.equals("card")) { + message = Localizer.getInstance().getMessage("lblChooseACardName"); + } else { + message = Localizer.getInstance().getMessage("lblChooseASpecificCard", validDesc); + } + boolean randomChoice = sa.hasParam("AtRandom"); boolean chooseFromDefined = sa.hasParam("ChooseFromDefinedCards"); for (final Player p : tgtPlayers) { @@ -100,12 +109,9 @@ public class ChooseCardNameEffect extends SpellAbilityEffect { } } Collections.sort(faces); - chosen = p.getController().chooseCardName(sa, faces, Localizer.getInstance().getMessage("lblChooseACardName")); + chosen = p.getController().chooseCardName(sa, faces, message); } else { - // use CardFace because you might name a alternate name - //"name a card" in mtg card oracle text is "choose a card name",change text - final String message = validDesc.equals("card") ? Localizer.getInstance().getMessage("lblChooseACardName") : Localizer.getInstance().getMessage("lblChooseASpecificCard", validDesc); - + // use CardFace because you might name a alternate names Predicate cpp = Predicates.alwaysTrue(); if (sa.hasParam("ValidCards")) { cpp = CardFacePredicates.valid(valid); @@ -119,6 +125,9 @@ public class ChooseCardNameEffect extends SpellAbilityEffect { p.getGame().getAction().nofityOfValue(sa, host, Localizer.getInstance().getMessage("lblPlayerPickedChosen", p.getName(), chosen), p); p.setNamedCard(chosen); } + if (sa.hasParam("NoteFor")) { + p.addNoteForName(sa.getParam("NoteFor"), "Name:" + chosen); + } } } } diff --git a/forge-game/src/main/java/forge/game/ability/effects/PumpEffect.java b/forge-game/src/main/java/forge/game/ability/effects/PumpEffect.java index 3f46f2aebd4..8cd637577ac 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/PumpEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/PumpEffect.java @@ -345,6 +345,14 @@ public class PumpEffect extends SpellAbilityEffect { pumpForget = sa.getParam("ForgetObjects"); } + if (sa.hasParam("NoteCardsFor")) { + for (final Card c : AbilityUtils.getDefinedCards(host, sa.getParam("NoteCards"), sa)) { + for (Player p : tgtPlayers) { + p.addNoteForName(sa.getParam("NoteCardsFor"), "Id:" + c.getId()); + } + } + } + if (pumpForget != null) { for (final Object o : AbilityUtils.getDefinedObjects(host, pumpForget, sa)) { host.removeRemembered(o); @@ -394,7 +402,7 @@ public class PumpEffect extends SpellAbilityEffect { if (sa.hasParam("AtEOT") && !tgtCards.isEmpty()) { registerDelayedTrigger(sa, sa.getParam("AtEOT"), tgtCards); } - + for (final Card tgtC : untargetedCards) { // only pump things in PumpZone if (!tgtC.isInZone(pumpZone)) { 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 4f639602fa8..194820a5e61 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -1817,7 +1817,7 @@ public class Card extends GameEntity implements Comparable { sbLong.append(getName()).append(" can block ") .append(CardType.getPluralType(k[1])) .append(" as though it had reach.\r\n"); - } else if (keyword.startsWith("MayEffectFromOpeningHand")) { + } else if (keyword.startsWith("MayEffectFromOpening")) { final String[] k = keyword.split(":"); // need to get SpellDescription from Svar String desc = AbilityFactory.getMapParams(getSVar(k[1])).get("SpellDescription"); diff --git a/forge-game/src/main/java/forge/game/card/CardProperty.java b/forge-game/src/main/java/forge/game/card/CardProperty.java index d325f11e771..53e52c0a69c 100644 --- a/forge-game/src/main/java/forge/game/card/CardProperty.java +++ b/forge-game/src/main/java/forge/game/card/CardProperty.java @@ -1759,6 +1759,17 @@ public class CardProperty { if (!card.isCommander()) { return false; } + } else if (property.startsWith("NotedFor")) { + final String key = property.substring("NotedFor".length()); + for (String note : sourceController.getNotesForName(key)) { + if (note.equals("Name:" + card.getName())) { + return true; + } + if (note.equals("Id:" + card.getId())) { + return true; + } + } + return false; } else { // StringType done in CardState if (!card.getCurrentState().hasProperty(property, sourceController, source, spellAbility)) { diff --git a/forge-game/src/main/java/forge/game/cost/Cost.java b/forge-game/src/main/java/forge/game/cost/Cost.java index 09f0b69b4d9..98bb194ff46 100644 --- a/forge-game/src/main/java/forge/game/cost/Cost.java +++ b/forge-game/src/main/java/forge/game/cost/Cost.java @@ -286,6 +286,8 @@ public class Cost implements Serializable { costParts.add(0, cp); } } + + sort(); } private static CostPart parseCostPart(String parse, boolean tapCost, boolean untapCost) { @@ -457,6 +459,12 @@ public class Cost implements Serializable { return new CostReveal(splitStr[0], splitStr[1], description); } + if (parse.startsWith("RevealFromExile<")) { + final String[] splitStr = abCostParse(parse, 3); + final String description = splitStr.length > 2 ? splitStr[2] : null; + return new CostReveal(splitStr[0], splitStr[1], description, ZoneType.Exile); + } + if (parse.startsWith("ExiledMoveToGrave<")) { final String[] splitStr = abCostParse(parse, 3); final String description = splitStr.length > 2 ? splitStr[2] : null; diff --git a/forge-game/src/main/java/forge/game/cost/CostReveal.java b/forge-game/src/main/java/forge/game/cost/CostReveal.java index b7d08ff1807..d854b5ccb7a 100644 --- a/forge-game/src/main/java/forge/game/cost/CostReveal.java +++ b/forge-game/src/main/java/forge/game/cost/CostReveal.java @@ -38,26 +38,37 @@ public class CostReveal extends CostPartWithList { */ private static final long serialVersionUID = 1L; + private ZoneType revealFrom = ZoneType.Hand; + public CostReveal(final String amount, final String type, final String description) { super(amount, type, description); } + public CostReveal(final String amount, final String type, final String description, final ZoneType zoneType) { + super(amount, type, description); + this.revealFrom = zoneType; + } + @Override public boolean isReusable() { return true; } @Override public boolean isRenewable() { return true; } + public ZoneType getRevealFrom() { + return revealFrom; + } + @Override public final boolean canPay(final SpellAbility ability, final Player payer) { final Card source = ability.getHostCard(); - CardCollectionView handList = payer.getCardsIn(ZoneType.Hand); + CardCollectionView handList = payer.getCardsIn(revealFrom); final String type = this.getType(); final Integer amount = this.convertAmount(); if (this.payCostFromSource()) { - return source.isInZone(ZoneType.Hand); + return source.isInZone(revealFrom); } else if (this.getType().equals("Hand")) { return true; } else if (this.getType().equals("SameColor")) { @@ -100,7 +111,7 @@ public class CostReveal extends CostPartWithList { if (this.payCostFromSource()) { sb.append(this.getType()); } else if (this.getType().equals("Hand")) { - return ("Reveal you hand"); + return ("Reveal your hand"); } else if (this.getType().equals("SameColor")) { return ("Reveal " + i + " cards from your hand that share a color"); } else { @@ -115,7 +126,9 @@ public class CostReveal extends CostPartWithList { sb.append(Cost.convertAmountTypeToWords(i, this.getAmount(), desc.toString())); } - sb.append(" from your hand"); + + sb.append(" from your "); + sb.append(revealFrom.getTranslatedName()); return sb.toString(); } @@ -154,4 +167,11 @@ public class CostReveal extends CostPartWithList { public T accept(ICostVisitor visitor) { return visitor.visit(this); } + + @Override + public int paymentOrder() { + // Caller of the Untamed needs the reveal to happen before the mana cost + if (!revealFrom.equals(ZoneType.Hand)) { return -1; } + return 5; + } } diff --git a/forge-game/src/main/java/forge/game/player/Player.java b/forge-game/src/main/java/forge/game/player/Player.java index 510041f2043..c910a9b3cfb 100644 --- a/forge-game/src/main/java/forge/game/player/Player.java +++ b/forge-game/src/main/java/forge/game/player/Player.java @@ -36,6 +36,7 @@ import forge.game.event.*; import forge.game.keyword.Keyword; import forge.game.keyword.KeywordCollection; import forge.game.keyword.KeywordCollection.KeywordCollectionView; +import forge.game.keyword.KeywordInterface; import forge.game.keyword.KeywordsChange; import forge.game.mana.ManaPool; import forge.game.phase.PhaseHandler; @@ -108,6 +109,7 @@ public class Player extends GameEntity implements Comparable { private int numDrawnThisDrawStep = 0; private int numDiscardedThisTurn = 0; private int numCardsInHandStartedThisTurnWith = 0; + private final Map> notes = Maps.newHashMap(); private boolean revolt = false; @@ -1601,6 +1603,19 @@ public class Player extends GameEntity implements Comparable { numCardsInHandStartedThisTurnWith = num; } + public void addNoteForName(String notedFor, String noted) { + if (!notes.containsKey(notedFor)) { + notes.put(notedFor, new FCollection<>()); + } + notes.get(notedFor).add(noted); + } + public FCollection getNotesForName(String notedFor) { + if (!notes.containsKey(notedFor)) { + notes.put(notedFor, new FCollection<>()); + } + return notes.get(notedFor); + } + public final CardCollectionView mill(final int n, final ZoneType destination, final boolean bottom, SpellAbility sa, CardZoneTable table) { final CardCollectionView lib = getCardsIn(ZoneType.Library); @@ -2748,6 +2763,21 @@ public class Player extends GameEntity implements Comparable { } com.add(conspire); } + + for (final Card c : getCardsIn(ZoneType.Library)) { + for (KeywordInterface inst : c.getKeywords()) { + String kw = inst.getOriginal(); + if (kw.startsWith("MayEffectFromOpeningDeck")) { + String[] split = kw.split(":"); + final String effName = split[1]; + + final SpellAbility effect = AbilityFactory.getAbility(c.getSVar(effName), c); + effect.setActivatingPlayer(this); + + getController().playSpellAbilityNoStack(effect, true); + } + } + } } public static DetachedCardEffect createCommanderEffect(Game game, Card commander) { diff --git a/forge-game/src/main/java/forge/game/spellability/SpellAbilityCondition.java b/forge-game/src/main/java/forge/game/spellability/SpellAbilityCondition.java index 0c3f01508a6..206a45380e9 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbilityCondition.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbilityCondition.java @@ -17,9 +17,11 @@ */ package forge.game.spellability; +import com.google.common.collect.Lists; import forge.card.MagicColor; import forge.game.Game; import forge.game.GameObject; +import forge.game.GameType; import forge.game.ability.AbilityUtils; import forge.game.card.Card; import forge.game.card.CardCollectionView; @@ -36,11 +38,7 @@ import org.apache.commons.lang3.StringUtils; import com.google.common.collect.Iterables; -import java.util.ArrayList; -import java.util.Set; -import java.util.HashSet; -import java.util.List; -import java.util.Map; +import java.util.*; /** *

@@ -147,6 +145,15 @@ public class SpellAbilityCondition extends SpellAbilityVariables { this.setPhases(PhaseType.parseRange(params.get("ConditionPhases"))); } + if (params.containsKey("ConditionGameTypes")) { + String[] gameTypeNames = params.get("ConditionGameTypes").split(","); + List gameTypes = Lists.newArrayList(); + for (String name : gameTypeNames) { + gameTypes.add(GameType.smartValueOf(name)); + } + this.setGameTypes(gameTypes); + } + if (params.containsKey("ConditionChosenColor")) { this.setColorToCheck(params.get("ConditionChosenColor")); } @@ -322,6 +329,13 @@ public class SpellAbilityCondition extends SpellAbilityVariables { } } + if (this.getGameTypes().size() > 0) { + GameType currGameType = sa.getHostCard().getGame().getRules().getGameType(); + if (!getGameTypes().contains(currGameType)) { + return false; + } + } + if (this.getCardsInHand() != -1) { // Can handle Library of Alexandria, or Hellbent if (activator.getCardsIn(ZoneType.Hand).size() != this.getCardsInHand()) { diff --git a/forge-game/src/main/java/forge/game/spellability/SpellAbilityRestriction.java b/forge-game/src/main/java/forge/game/spellability/SpellAbilityRestriction.java index 35c63ba7d0a..8f454a9f725 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbilityRestriction.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbilityRestriction.java @@ -20,7 +20,9 @@ package forge.game.spellability; import java.util.List; import java.util.Map; +import com.google.common.collect.Lists; import forge.game.Game; +import forge.game.GameType; import forge.game.ability.AbilityUtils; import forge.game.card.Card; import forge.game.card.CardCollectionView; @@ -141,6 +143,15 @@ public class SpellAbilityRestriction extends SpellAbilityVariables { this.setPhases(PhaseType.parseRange(params.get("ActivationPhases"))); } + if (params.containsKey("ActivationGameTypes")) { + String[] gameTypeNames = params.get("ActivationGameTypes").split(","); + List gameTypes = Lists.newArrayList(); + for (String name : gameTypeNames) { + gameTypes.add(GameType.smartValueOf(name)); + } + this.setGameTypes(gameTypes); + } + if (params.containsKey("ActivationCardsInHand")) { this.setActivateCardsInHand(Integer.parseInt(params.get("ActivationCardsInHand"))); } diff --git a/forge-game/src/main/java/forge/game/spellability/SpellAbilityVariables.java b/forge-game/src/main/java/forge/game/spellability/SpellAbilityVariables.java index 7f8c32e6f74..dbe3ad01f06 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbilityVariables.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbilityVariables.java @@ -21,6 +21,7 @@ import java.util.List; import com.google.common.collect.Lists; +import forge.game.GameType; import forge.game.phase.PhaseType; import forge.game.zone.ZoneType; @@ -98,6 +99,9 @@ public class SpellAbilityVariables implements Cloneable { /** The phases. */ private List phases = Lists.newArrayList(); + /** The GameTypes */ + private List gameTypes = Lists.newArrayList(); + /** The b sorcery speed. */ private boolean sorcerySpeed = false; @@ -386,6 +390,18 @@ public class SpellAbilityVariables implements Cloneable { this.phases.addAll(phases); } + /** + *

+ * Setter for the field gameTypes. + *

+ * + * @param gameTypes + */ + public final void setGameTypes(final List gameTypes) { + this.gameTypes.clear(); + this.gameTypes.addAll(gameTypes); + } + /** *

* setActivateCardsInHand. @@ -686,6 +702,15 @@ public class SpellAbilityVariables implements Cloneable { return this.phases; } + /** + * Gets the game types. + * + * @return the phases + */ + public final List getGameTypes() { + return this.gameTypes; + } + /** * Gets the present defined. diff --git a/forge-gui/res/cardsfolder/a/arcane_savant.txt b/forge-gui/res/cardsfolder/a/arcane_savant.txt new file mode 100644 index 00000000000..6da8043b0f3 --- /dev/null +++ b/forge-gui/res/cardsfolder/a/arcane_savant.txt @@ -0,0 +1,13 @@ +Name:Arcane Savant +ManaCost:3 U U +Types:Creature Human Wizard +PT:3/3 +K:MayEffectFromOpeningDeck:DBReveal +SVar:DBReveal:DB$ Reveal | RevealDefined$ Self | SubAbility$ DBExile | SpellDescription$ Before you shuffle your deck to start the game, you may reveal this card from your deck and exile an instant or sorcery card you drafted that isn’t in your deck. +SVar:DBExile:DB$ ChangeZone | Origin$ Sideboard | Destination$ Exile | ChangeType$ Instant.YouOwn,Sorcery.YouOwn | ChangeNum$ 1 | Optional$ True | RememberChanged$ True | SubAbility$ DBPump | ConditionGameTypes$ Draft,QuestDraft | SelectPrompt$ Exile with Arcane Savant +SVar:DBPump:DB$ Pump | NoteCards$ Remembered | NoteCardsFor$ ArcaneSavant | SubAbility$ DBCleanup +SVar:DBCleanup:DB$Cleanup | ClearRemembered$ True +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigPlay | TriggerDescription$ When CARDNAME enters the battlefield, copy a card you exiled with cards named Arcane Savant. You may cast the copy without paying its mana cost. +SVar:TrigPlay:DB$ Play | Valid$ Card.YouOwn+NotedForArcaneSavant | ValidZone$ Exile | Amount$ 1 | CopyOnce$ True | WithoutManaCost$ True | Optional$ True | CopyCard$ True | SpellDescription$ You may copy the exiled card. If you do, you may cast the copy without paying its mana cost. | SubAbility$ DBCleanup +SVar:Picture:https://img.scryfall.com/cards/large/en/cn2/27.jpg?1517813031 +Oracle:Before you shuffle your deck to start the game, you may reveal this card from your deck and exile an instant or sorcery card you drafted that isn’t in your deck.\nWhen Arcane Savant enters the battlefield, copy a card you exiled with cards named Arcane Savant. You may cast the copy without paying its mana cost. diff --git a/forge-gui/res/cardsfolder/c/caller_of_the_untamed.txt b/forge-gui/res/cardsfolder/c/caller_of_the_untamed.txt new file mode 100644 index 00000000000..3725c19d6cc --- /dev/null +++ b/forge-gui/res/cardsfolder/c/caller_of_the_untamed.txt @@ -0,0 +1,13 @@ +Name:Caller of the Untamed +ManaCost:3 G +Types:Creature Elf Shaman +PT:2/4 +K:MayEffectFromOpeningDeck:DBReveal +SVar:DBReveal:DB$ Reveal | RevealDefined$ Self | SubAbility$ DBExile | SpellDescription$ Before you shuffle your deck to start the game, you may reveal this card from your deck and exile an instant or sorcery card you drafted that isn’t in your deck. +SVar:DBExile:DB$ ChangeZone | Origin$ Sideboard | Destination$ Exile | ChangeType$ Creature.YouOwn | ChangeNum$ 1 | Optional$ True | RememberChanged$ True | SubAbility$ DBPump | ConditionGameTypes$ Draft,QuestDraft | SelectPrompt$ Exile with Caller of the Untamed +SVar:DBPump:DB$ Pump | NoteCards$ Remembered | NoteCardsFor$ CallerOfTheUntamed | SubAbility$ DBCleanup +SVar:DBCleanup:DB$Cleanup | ClearRemembered$ True +A:AB$ CopyPermanent | Cost$ RevealFromExile<1/Creature.YouOwn+NotedForCallerOfTheUntamed> X T | CostDesc$ {X}, {T} | Defined$ Revealed | References$ X | SpellDescription$ Create a token that’s a copy of a card you exiled with cards named Caller of the Untamed. X is the converted mana cost of that card. | SubAbility$ DBCleanup +SVar:X:Revealed$CardManaCost +SVar:Picture:https://img.scryfall.com/cards/large/en/cn2/62.jpg?1517813031 +Oracle:Before you shuffle your deck to start the game, you may reveal this card from your deck and exile a creature card you drafted that isn’t in your deck.\n{X}, {T}: Create a token that’s a copy of a card you exiled with cards named Caller of the Untamed. X is the converted mana cost of that card. diff --git a/forge-gui/res/cardsfolder/v/volatile_chimera.txt b/forge-gui/res/cardsfolder/v/volatile_chimera.txt new file mode 100644 index 00000000000..c7b39098954 --- /dev/null +++ b/forge-gui/res/cardsfolder/v/volatile_chimera.txt @@ -0,0 +1,16 @@ +Name:Volatile Chimera +ManaCost:2 R +Types:Creature Elemental Chimera +PT:3/2 +K:MayEffectFromOpeningDeck:DBReveal +SVar:DBReveal:DB$ Reveal | RevealDefined$ Self | SubAbility$ DBChoose | SpellDescription$ Before you shuffle your deck to start the game, you may reveal this card from your deck and exile three or more creature cards you drafted that aren't in your deck. +SVar:DBChoose:DB$ ChooseCard | MinAmount$ 0 | Amount$ X | References$ X | Choices$ Creature.YouOwn | ChoiceTitle$ Exile with Volatile Chimera | ChoiceZone$ Sideboard | RememberChosen$ True | ConditionGameTypes$ Draft,QuestDraft | SubAbility$ DBExile +SVar:DBExile:DB$ ChangeZone | Origin$ Sideboard | Destination$ Exile | Defined$ Remembered | ConditionCheckSVar$ Y | ConditionSVarCompare$ GE3 | SubAbility$ DBPump | References$ Y +SVar:DBPump:DB$ Pump | NoteCards$ Remembered | NoteCardsFor$ VolatileChimera | SubAbility$ DBCleanup +SVar:DBCleanup:DB$Cleanup | ClearRemembered$ True +A:AB$ ChooseCard | Cost$ 1 R | ChoiceZone$ Exile | AtRandom$ True | Choices$ Card.YouOwn+NotedForVolatileChimera | SubAbility$ DBClone | SpellDescription$ Choose a card at random you exiled with cards named Volatile Chimera. Volatile Chimera becomes a copy of that card, except it has this ability. +SVar:DBClone:DB$ Clone | Defined$ ChosenCard | GainThisAbility$ True +SVar:X:Count$InYourSideboard +SVar:Y:Count$RememberedSize +SVar:Picture:https://img.scryfall.com/cards/large/en/cn2/62.jpg?1517813031 +Oracle:Before you shuffle your deck to start the game, you may reveal this card from your deck and exile three or more creature cards you drafted that aren't in your deck.\n{1}{R}: Choose a card at random you exiled with cards named Volatile Chimera. Volatile Chimera becomes a copy of that card, except it has this ability. diff --git a/forge-gui/src/main/java/forge/player/HumanCostDecision.java b/forge-gui/src/main/java/forge/player/HumanCostDecision.java index 22d379ddf5b..09c908b481c 100644 --- a/forge-gui/src/main/java/forge/player/HumanCostDecision.java +++ b/forge-gui/src/main/java/forge/player/HumanCostDecision.java @@ -811,7 +811,7 @@ public class HumanCostDecision extends CostDecisionMakerBase { InputSelectCardsFromList inp = null; if (cost.getType().equals("SameColor")) { final Integer num = cost.convertAmount(); - CardCollectionView hand = player.getCardsIn(ZoneType.Hand); + CardCollectionView hand = player.getCardsIn(cost.getRevealFrom()); final CardCollectionView hand2 = hand; hand = CardLists.filter(hand, new Predicate() { @Override @@ -844,7 +844,7 @@ public class HumanCostDecision extends CostDecisionMakerBase { else { Integer num = cost.convertAmount(); - CardCollectionView hand = player.getCardsIn(ZoneType.Hand); + CardCollectionView hand = player.getCardsIn(cost.getRevealFrom()); hand = CardLists.getValidCards(hand, cost.getType().split(";"), player, source, ability); if (num == null) { @@ -861,6 +861,9 @@ public class HumanCostDecision extends CostDecisionMakerBase { if (num == 0) { return PaymentDecision.number(0); } + if (hand.size() == num) { + return PaymentDecision.card(hand); + } inp = new InputSelectCardsFromList(controller, num, num, hand, ability); inp.setMessage("Select %d more " + cost.getDescriptiveType() + " card(s) to reveal."); diff --git a/forge-gui/src/main/java/forge/player/HumanPlay.java b/forge-gui/src/main/java/forge/player/HumanPlay.java index e7282d4cf4f..a8cf3e0ac8d 100644 --- a/forge-gui/src/main/java/forge/player/HumanPlay.java +++ b/forge-gui/src/main/java/forge/player/HumanPlay.java @@ -606,7 +606,8 @@ public class HumanPlay { } } else if (part instanceof CostReveal) { - CardCollectionView list = CardLists.getValidCards(p.getCardsIn(ZoneType.Hand), part.getType(), p, source); + CostReveal costReveal = (CostReveal) part; + CardCollectionView list = CardLists.getValidCards(p.getCardsIn(costReveal.getRevealFrom()), part.getType(), p, source); int amount = getAmountFromPartX(part, source, sourceAbility); boolean hasPaid = payCostPart(controller, sourceAbility, (CostPartWithList)part, amount, list, Localizer.getInstance().getMessage("lblReveal") + orString); if (!hasPaid) { return false; }