diff --git a/forge-ai/src/main/java/forge/ai/SpellApiToAi.java b/forge-ai/src/main/java/forge/ai/SpellApiToAi.java index c95593f5c71..ae17f988dc7 100644 --- a/forge-ai/src/main/java/forge/ai/SpellApiToAi.java +++ b/forge-ai/src/main/java/forge/ai/SpellApiToAi.java @@ -98,6 +98,7 @@ public enum SpellApiToAi { .put(ApiType.Learn, LearnAi.class) .put(ApiType.LoseLife, LifeLoseAi.class) .put(ApiType.LosesGame, GameLossAi.class) + .put(ApiType.MakeCard, AlwaysPlayAi.class) .put(ApiType.Mana, ManaEffectAi.class) .put(ApiType.ManaReflected, CannotPlayAi.class) .put(ApiType.Manifest, ManifestAi.class) diff --git a/forge-game/src/main/java/forge/game/ability/ApiType.java b/forge-game/src/main/java/forge/game/ability/ApiType.java index e540e28f6b1..c528e15ce83 100644 --- a/forge-game/src/main/java/forge/game/ability/ApiType.java +++ b/forge-game/src/main/java/forge/game/ability/ApiType.java @@ -97,6 +97,7 @@ public enum ApiType { LookAt (LookAtEffect.class), LoseLife (LifeLoseEffect.class), LosesGame (GameLossEffect.class), + MakeCard (MakeCardEffect.class), Mana (ManaEffect.class), ManaReflected (ManaReflectedEffect.class), Manifest (ManifestEffect.class), diff --git a/forge-game/src/main/java/forge/game/ability/effects/MakeCardEffect.java b/forge-game/src/main/java/forge/game/ability/effects/MakeCardEffect.java new file mode 100644 index 00000000000..7da3ab8f3a6 --- /dev/null +++ b/forge-game/src/main/java/forge/game/ability/effects/MakeCardEffect.java @@ -0,0 +1,38 @@ +package forge.game.ability.effects; + +import forge.StaticData; +import forge.game.Game; +import forge.game.ability.SpellAbilityEffect; +import forge.game.card.Card; +import forge.game.card.CardCollection; +import forge.game.player.Player; +import forge.game.spellability.SpellAbility; +import forge.game.zone.ZoneType; + +public class MakeCardEffect extends SpellAbilityEffect { + @Override + public void resolve(SpellAbility sa) { + final Player player = sa.getActivatingPlayer(); + final Game game = player.getGame(); + + final String name = sa.hasParam("Name") ? sa.getParam("Name") : sa.getHostCard().getName(); + final ZoneType zone = ZoneType.smartValueOf(sa.getParamOrDefault("Zone", "Library")); + int amount = sa.hasParam("Amount") ? Integer.parseInt(sa.getParam("Amount")) : 1; + + CardCollection cards = new CardCollection(); + + while (amount > 0) { + Card card = Card.fromPaperCard(StaticData.instance().getCommonCards().getUniqueByName(name), player); + if (!sa.hasParam("NotToken")) { card.setTokenCard(true); } + cards.add(card); + amount--; + } + + for (final Card c : cards) { + game.getAction().moveTo(zone, c, sa); + } + if (zone.equals(ZoneType.Library)) { + player.shuffle(sa); + } + } +} 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 0ea766d0c02..fe9533a4234 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -222,6 +222,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars { private boolean tapped = false; private boolean sickness = true; // summoning sickness private boolean token = false; + private boolean tokenCard = false; private Card copiedPermanent = null; private boolean copiedSpell = false; @@ -2921,6 +2922,18 @@ public class Card extends GameEntity implements Comparable, IHasSVars { view.updateToken(this); } + public final boolean isTokenCard() { + if (isInZone(ZoneType.Battlefield) && hasMergedCard()) { + return getTopMergedCard().tokenCard; + } + return tokenCard; + } + public final void setTokenCard(boolean tokenC) { + if (tokenCard = tokenC) { return; } + tokenCard = tokenC; + view.updateTokenCard(this); + } + public final Card getCopiedPermanent() { return copiedPermanent; } diff --git a/forge-game/src/main/java/forge/game/card/CardPredicates.java b/forge-game/src/main/java/forge/game/card/CardPredicates.java index 9dff8d1bfb7..4f4847586fa 100644 --- a/forge-game/src/main/java/forge/game/card/CardPredicates.java +++ b/forge-game/src/main/java/forge/game/card/CardPredicates.java @@ -608,7 +608,7 @@ public final class CardPredicates { public static final Predicate NON_TOKEN = new Predicate() { @Override public boolean apply(Card c) { - return !c.isToken(); + return !(c.isToken() || c.isTokenCard()); } }; /** @@ -617,7 +617,7 @@ public final class CardPredicates { public static final Predicate TOKEN = new Predicate() { @Override public boolean apply(Card c) { - return c.isToken(); + return c.isToken() || c.isTokenCard(); } }; /** 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 fb1d431938b..8d08a469332 100644 --- a/forge-game/src/main/java/forge/game/card/CardProperty.java +++ b/forge-game/src/main/java/forge/game/card/CardProperty.java @@ -1370,11 +1370,11 @@ public class CardProperty { return false; } } else if (property.startsWith("token")) { - if (!card.isToken()) { + if (!card.isToken() && !card.isTokenCard()) { return false; } } else if (property.startsWith("nonToken")) { - if (card.isToken()) { + if (card.isToken() || card.isTokenCard()) { return false; } } else if (property.startsWith("copiedSpell")) { diff --git a/forge-game/src/main/java/forge/game/card/CardView.java b/forge-game/src/main/java/forge/game/card/CardView.java index aa0a182e7bd..2562e980e9f 100644 --- a/forge-game/src/main/java/forge/game/card/CardView.java +++ b/forge-game/src/main/java/forge/game/card/CardView.java @@ -233,6 +233,9 @@ public class CardView extends GameEntityView { set(TrackableProperty.Token, c.isToken()); } + public boolean isTokenCard() { return get(TrackableProperty.TokenCard); } + void updateTokenCard(Card c) { set(TrackableProperty.TokenCard, c.isTokenCard()); } + public boolean isCommander() { return get(TrackableProperty.IsCommander); } diff --git a/forge-game/src/main/java/forge/trackable/TrackableProperty.java b/forge-game/src/main/java/forge/trackable/TrackableProperty.java index 86f11dde61a..ab5db9e2836 100644 --- a/forge-game/src/main/java/forge/trackable/TrackableProperty.java +++ b/forge-game/src/main/java/forge/trackable/TrackableProperty.java @@ -47,6 +47,7 @@ public enum TrackableProperty { Sickness(TrackableTypes.BooleanType), Tapped(TrackableTypes.BooleanType), Token(TrackableTypes.BooleanType), + TokenCard(TrackableTypes.BooleanType), IsCommander(TrackableTypes.BooleanType), CommanderAltType(TrackableTypes.StringType), Damage(TrackableTypes.IntegerType), diff --git a/forge-gui/res/cardsfolder/b/bone_rattler.txt b/forge-gui/res/cardsfolder/b/bone_rattler.txt new file mode 100644 index 00000000000..034076898ac --- /dev/null +++ b/forge-gui/res/cardsfolder/b/bone_rattler.txt @@ -0,0 +1,10 @@ +Name:Bone Rattler +ManaCost:3 B B +Types:Creature Skeleton +PT:4/4 +T:Mode$ ChangesZone | ValidCard$ Card.Self | Origin$ Any | Destination$ Graveyard | Execute$ TrigExile | TriggerDescription$ When CARDNAME is put into a graveyard from anywhere, exile it. If you do, create four Reassembling Skeleton token cards and put them into your graveyard. +SVar:TrigExile:DB$ ChangeZone | Origin$ Graveyard | Destination$ Exile | RememberChanged$ True | SubAbility$ DBMakeCard +SVar:DBMakeCard:DB$ MakeCard | Name$ Reassembling Skeleton | Zone$ Graveyard | Amount$ 4 | SubAbility$ DBCleanup | ConditionDefined$ Remembered | ConditionPresent$ Card.Self | ConditionCompare$ GE1 +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +DeckHas:Ability$Graveyard +Oracle:When Bone Rattler is put into a graveyard from anywhere, exile it. If you do, create four Reassembling Skeleton token cards and put them into your graveyard. diff --git a/forge-gui/res/cardsfolder/t/time_sidewalk.txt b/forge-gui/res/cardsfolder/t/time_sidewalk.txt new file mode 100644 index 00000000000..1cf808dc2a0 --- /dev/null +++ b/forge-gui/res/cardsfolder/t/time_sidewalk.txt @@ -0,0 +1,9 @@ +Name:Time Sidewalk +ManaCost:4 U U U U +Types:Sorcery +K:MayEffectFromOpeningHand:ExileCard +A:SP$ AddTurn | Cost$ 4 U U U U | NumTurns$ 1 | SpellDescription$ Take an extra turn after this one. +SVar:ExileCard:DB$ ChangeZone | Defined$ Self | Origin$ Hand | Destination$ Exile | RememberChanged$ True | SubAbility$ DBMakeCard | SpellDescription$ If this card is in your opening hand, you may exile it. If you do, create four Time Walk token cards and shuffle them into your deck. +SVar:DBMakeCard:DB$ MakeCard | Name$ Time Walk | Amount$ 4 | Zone$ Library | SubAbility$ DBCleanup | ConditionDefined$ Remembered | ConditionPresent$ Card.Self | ConditionCompare$ GE1 +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +Oracle:Take an extra turn after this one.\nIf this card is in your opening hand, you may exile it. If you do, create four Time Walk token cards and shuffle them into your deck. diff --git a/forge-gui/src/main/java/forge/gui/card/CardDetailUtil.java b/forge-gui/src/main/java/forge/gui/card/CardDetailUtil.java index 2e81bd8720e..1d3df5bad43 100644 --- a/forge-gui/src/main/java/forge/gui/card/CardDetailUtil.java +++ b/forge-gui/src/main/java/forge/gui/card/CardDetailUtil.java @@ -280,6 +280,8 @@ public class CardDetailUtil { area.append("Emblem"); else area.append("Token"); + } else if (card.isTokenCard()) { + area.append("Token card"); } // card text