LCI: deeproot_pilgrimage.txt and support (TapAllTrigger) (#4043)

* LCI: deeproot_pilgrimage.txt and support (TapAllTrigger)

* refactor Card.tap to boolean

* move tappedAll check out of CostPartWithList
This commit is contained in:
Northmoc
2023-11-14 09:13:25 -05:00
committed by GitHub
parent ea81c03deb
commit 183030f53c
18 changed files with 196 additions and 25 deletions

View File

@@ -4,8 +4,12 @@ import forge.game.Game;
import forge.game.ability.AbilityKey; import forge.game.ability.AbilityKey;
import forge.game.ability.SpellAbilityEffect; import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.event.GameEventCardRegenerated; import forge.game.event.GameEventCardRegenerated;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.trigger.TriggerType;
import java.util.Map;
public class RegenerationEffect extends SpellAbilityEffect { public class RegenerationEffect extends SpellAbilityEffect {
@@ -17,6 +21,7 @@ public class RegenerationEffect extends SpellAbilityEffect {
public void resolve(SpellAbility sa) { public void resolve(SpellAbility sa) {
final Card host = sa.getHostCard(); final Card host = sa.getHostCard();
final Game game = host.getGame(); final Game game = host.getGame();
CardCollection tapped = new CardCollection();
for (Card c : getTargetCards(sa)) { for (Card c : getTargetCards(sa)) {
// checks already done in ReplacementEffect // checks already done in ReplacementEffect
@@ -24,7 +29,7 @@ public class RegenerationEffect extends SpellAbilityEffect {
c.setDamage(0); c.setDamage(0);
c.setHasBeenDealtDeathtouchDamage(false); c.setHasBeenDealtDeathtouchDamage(false);
c.tap(true, cause, c.getController()); if (c.tap(true, cause, c.getController())) tapped.add(c);
c.addRegeneratedThisTurn(); c.addRegeneratedThisTurn();
if (game.getCombat() != null) { if (game.getCombat() != null) {
@@ -40,6 +45,11 @@ public class RegenerationEffect extends SpellAbilityEffect {
host.removeRemembered(c); host.removeRemembered(c);
} }
} }
if (!tapped.isEmpty()) {
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
runParams.put(AbilityKey.Cards, tapped);
game.getTriggerHandler().runTrigger(TriggerType.TapAll, runParams, false);
}
} }
} }

View File

@@ -1,15 +1,20 @@
package forge.game.ability.effects; package forge.game.ability.effects;
import forge.game.Game; import forge.game.Game;
import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect; import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView; import forge.game.card.CardCollectionView;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.AbilitySub; import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.trigger.TriggerType;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import java.util.Map;
public class TapAllEffect extends SpellAbilityEffect { public class TapAllEffect extends SpellAbilityEffect {
@Override @Override
protected String getStackDescription(SpellAbility sa) { protected String getStackDescription(SpellAbility sa) {
@@ -41,6 +46,7 @@ public class TapAllEffect extends SpellAbilityEffect {
Player tapper = activator; Player tapper = activator;
CardCollection tapped = new CardCollection();
for (final Card c : cards) { for (final Card c : cards) {
if (remTapped) { if (remTapped) {
card.addRemembered(c); card.addRemembered(c);
@@ -48,7 +54,12 @@ public class TapAllEffect extends SpellAbilityEffect {
if (sa.hasParam("TapperController")) { if (sa.hasParam("TapperController")) {
tapper = c.getController(); tapper = c.getController();
} }
c.tap(true, sa, tapper); if (c.tap(true, sa, tapper)) tapped.add(c);
}
if (!tapped.isEmpty()) {
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
runParams.put(AbilityKey.Cards, tapped);
game.getTriggerHandler().runTrigger(TriggerType.TapAll, runParams, false);
} }
} }

View File

@@ -1,5 +1,6 @@
package forge.game.ability.effects; package forge.game.ability.effects;
import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect; import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card; import forge.game.card.Card;
@@ -7,10 +8,13 @@ import forge.game.card.CardCollection;
import forge.game.card.CardLists; import forge.game.card.CardLists;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.trigger.TriggerType;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.util.Lang; import forge.util.Lang;
import forge.util.Localizer; import forge.util.Localizer;
import java.util.Map;
public class TapEffect extends SpellAbilityEffect { public class TapEffect extends SpellAbilityEffect {
/* (non-Javadoc) /* (non-Javadoc)
@@ -45,6 +49,7 @@ public class TapEffect extends SpellAbilityEffect {
tapper = AbilityUtils.getDefinedPlayers(card, sa.getParam("Tapper"), sa).getFirst(); tapper = AbilityUtils.getDefinedPlayers(card, sa.getParam("Tapper"), sa).getFirst();
} }
CardCollection tapped = new CardCollection();
for (final Card tgtC : toTap) { for (final Card tgtC : toTap) {
if (tgtC.isPhasedOut()) { if (tgtC.isPhasedOut()) {
continue; continue;
@@ -53,13 +58,18 @@ public class TapEffect extends SpellAbilityEffect {
if (tgtC.isUntapped() && remTapped || alwaysRem) { if (tgtC.isUntapped() && remTapped || alwaysRem) {
card.addRemembered(tgtC); card.addRemembered(tgtC);
} }
tgtC.tap(true, sa, tapper); if (tgtC.tap(true, sa, tapper)) tapped.add(tgtC);
} }
if (sa.hasParam("ETB")) { if (sa.hasParam("ETB")) {
// do not fire Taps triggers // do not fire Taps triggers
tgtC.setTapped(true); tgtC.setTapped(true);
} }
} }
if (!tapped.isEmpty()) {
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
runParams.put(AbilityKey.Cards, tapped);
activator.getGame().getTriggerHandler().runTrigger(TriggerType.TapAll, runParams, false);
}
} }
@Override @Override

View File

@@ -2,18 +2,23 @@ package forge.game.ability.effects;
import forge.game.Game; import forge.game.Game;
import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect; import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView; import forge.game.card.CardCollectionView;
import forge.game.card.CardLists; import forge.game.card.CardLists;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.player.PlayerController; import forge.game.player.PlayerController;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.trigger.TriggerType;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.util.Lang; import forge.util.Lang;
import forge.util.Localizer; import forge.util.Localizer;
import java.util.Map;
public class TapOrUntapAllEffect extends SpellAbilityEffect { public class TapOrUntapAllEffect extends SpellAbilityEffect {
@Override @Override
@@ -60,16 +65,22 @@ public class TapOrUntapAllEffect extends SpellAbilityEffect {
toTap = sa.getActivatingPlayer().getController().chooseBinary(sa, sb.toString(), PlayerController.BinaryChoiceType.TapOrUntap); toTap = sa.getActivatingPlayer().getController().chooseBinary(sa, sb.toString(), PlayerController.BinaryChoiceType.TapOrUntap);
CardCollection tapped = new CardCollection();
for (final Card tgtC : validCards) { for (final Card tgtC : validCards) {
if (!tgtC.isInPlay()) { if (!tgtC.isInPlay()) {
continue; continue;
} }
if (toTap) { if (toTap) {
tgtC.tap(true, sa, activator); if (tgtC.tap(true, sa, activator)) tapped.add(tgtC);
} else { } else {
tgtC.untap(true); tgtC.untap(true);
} }
} }
if (!tapped.isEmpty()) {
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
runParams.put(AbilityKey.Cards, tapped);
game.getTriggerHandler().runTrigger(TriggerType.TapAll, runParams, false);
}
} }
} }

View File

@@ -1,15 +1,20 @@
package forge.game.ability.effects; package forge.game.ability.effects;
import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect; import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.player.PlayerController; import forge.game.player.PlayerController;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.trigger.TriggerType;
import forge.util.CardTranslation; import forge.util.CardTranslation;
import forge.util.Lang; import forge.util.Lang;
import forge.util.Localizer; import forge.util.Localizer;
import java.util.Map;
public class TapOrUntapEffect extends SpellAbilityEffect { public class TapOrUntapEffect extends SpellAbilityEffect {
/* (non-Javadoc) /* (non-Javadoc)
@@ -32,6 +37,7 @@ public class TapOrUntapEffect extends SpellAbilityEffect {
Player activator = sa.getActivatingPlayer(); Player activator = sa.getActivatingPlayer();
PlayerController pc = activator.getController(); PlayerController pc = activator.getController();
CardCollection tapped = new CardCollection();
for (final Card tgtC : getTargetCards(sa)) { for (final Card tgtC : getTargetCards(sa)) {
if (!tgtC.isInPlay()) { if (!tgtC.isInPlay()) {
continue; continue;
@@ -50,11 +56,16 @@ public class TapOrUntapEffect extends SpellAbilityEffect {
tapper = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Tapper"), sa).getFirst(); tapper = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Tapper"), sa).getFirst();
} }
tgtC.tap(true, sa, tapper); if (tgtC.tap(true, sa, tapper)) tapped.add(tgtC);
} else { } else {
tgtC.untap(true); tgtC.untap(true);
} }
} }
if (!tapped.isEmpty()) {
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
runParams.put(AbilityKey.Cards, tapped);
activator.getGame().getTriggerHandler().runTrigger(TriggerType.TapAll, runParams, false);
}
} }
} }

View File

@@ -4439,11 +4439,11 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
view.updateTapped(this); view.updateTapped(this);
} }
public final void tap(boolean tapAnimation, SpellAbility cause, Player tapper) { public final boolean tap(boolean tapAnimation, SpellAbility cause, Player tapper) {
tap(false, tapAnimation, cause, tapper); return tap(false, tapAnimation, cause, tapper);
} }
public final void tap(boolean attacker, boolean tapAnimation, SpellAbility cause, Player tapper) { public final boolean tap(boolean attacker, boolean tapAnimation, SpellAbility cause, Player tapper) {
if (tapped) { return; } if (tapped) { return false; }
// Run replacement effects // Run replacement effects
getGame().getReplacementHandler().run(ReplacementType.Tap, AbilityKey.mapFromAffected(this)); getGame().getReplacementHandler().run(ReplacementType.Tap, AbilityKey.mapFromAffected(this));
@@ -4458,6 +4458,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
setTapped(true); setTapped(true);
view.updateNeedsTapAnimation(tapAnimation); view.updateNeedsTapAnimation(tapAnimation);
getGame().fireEvent(new GameEventCardTapped(this, true)); getGame().fireEvent(new GameEventCardTapped(this, true));
return true;
} }
public final void untap(boolean untapAnimation) { public final void untap(boolean untapAnimation) {

View File

@@ -4,6 +4,8 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import forge.game.ability.AbilityKey;
import forge.game.trigger.TriggerType;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import com.google.common.base.Strings; import com.google.common.base.Strings;
@@ -279,26 +281,36 @@ public class CostAdjustment {
// GetSpellCostChange // GetSpellCostChange
private static void adjustCostByConvokeOrImprovise(ManaCostBeingPaid cost, final SpellAbility sa, boolean improvise, boolean test) { private static void adjustCostByConvokeOrImprovise(ManaCostBeingPaid cost, final SpellAbility sa, boolean improvise, boolean test) {
CardCollectionView untappedCards = CardLists.filter(sa.getActivatingPlayer().getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.UNTAPPED); final Player activator = sa.getActivatingPlayer();
CardCollectionView untappedCards = CardLists.filter(activator.getCardsIn(ZoneType.Battlefield),
CardPredicates.Presets.UNTAPPED);
if (improvise) { if (improvise) {
untappedCards = CardLists.filter(untappedCards, CardPredicates.Presets.ARTIFACTS); untappedCards = CardLists.filter(untappedCards, CardPredicates.Presets.ARTIFACTS);
} else { } else {
untappedCards = CardLists.filter(untappedCards, CardPredicates.Presets.CREATURES); untappedCards = CardLists.filter(untappedCards, CardPredicates.Presets.CREATURES);
} }
Map<Card, ManaCostShard> convokedCards = sa.getActivatingPlayer().getController().chooseCardsForConvokeOrImprovise(sa, cost.toManaCost(), untappedCards, improvise); Map<Card, ManaCostShard> convokedCards = activator.getController().chooseCardsForConvokeOrImprovise(sa,
cost.toManaCost(), untappedCards, improvise);
// Convoked creats are tapped here, setting up their taps triggers, // Convoked creats are tapped here, setting up their taps triggers,
// Then again when payment is done(In InputPayManaCost.done()) with suppression of Taps triggers. // Then again when payment is done(In InputPayManaCost.done()) with suppression of Taps triggers.
// This is to make sure that triggers go off at the right time // This is to make sure that triggers go off at the right time
// AND that you can't use mana tapabilities of convoked creatures to pay the convoked cost. // AND that you can't use mana tapabilities of convoked creatures to pay the convoked cost.
CardCollection tapped = new CardCollection();
for (final Entry<Card, ManaCostShard> conv : convokedCards.entrySet()) { for (final Entry<Card, ManaCostShard> conv : convokedCards.entrySet()) {
sa.addTappedForConvoke(conv.getKey()); Card c = conv.getKey();
sa.addTappedForConvoke(c);
cost.decreaseShard(conv.getValue(), 1); cost.decreaseShard(conv.getValue(), 1);
if (!test) { if (!test) {
conv.getKey().tap(true, sa, sa.getActivatingPlayer()); if (c.tap(true, sa, activator)) tapped.add(c);
} }
} }
if (!tapped.isEmpty()) {
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
runParams.put(AbilityKey.Cards, tapped);
activator.getGame().getTriggerHandler().runTrigger(TriggerType.TapAll, runParams, false);
}
} }
private static void adjustCostByOffering(final ManaCostBeingPaid cost, final SpellAbility sa) { private static void adjustCostByOffering(final ManaCostBeingPaid cost, final SpellAbility sa) {

View File

@@ -74,7 +74,12 @@ public class CostEnlist extends CostPartWithTrigger {
@Override @Override
protected Card doPayment(Player payer, SpellAbility ability, Card targetCard, final boolean effect) { protected Card doPayment(Player payer, SpellAbility ability, Card targetCard, final boolean effect) {
targetCard.tap(true, ability, payer); if (targetCard.tap(true, ability, payer)) {
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
runParams.put(AbilityKey.Cards, new CardCollection(targetCard));
payer.getGame().getTriggerHandler().runTrigger(TriggerType.TapAll, runParams, false);
}
// need to transfer info // need to transfer info
payTrig.addRemembered(targetCard); payTrig.addRemembered(targetCard);

View File

@@ -17,9 +17,14 @@
*/ */
package forge.game.cost; package forge.game.cost;
import forge.game.ability.AbilityKey;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.trigger.TriggerType;
import java.util.Map;
/** /**
* The Class CostTap. * The Class CostTap.
@@ -66,7 +71,12 @@ public class CostTap extends CostPart {
@Override @Override
public boolean payAsDecided(Player payer, PaymentDecision decision, SpellAbility ability, final boolean effect) { public boolean payAsDecided(Player payer, PaymentDecision decision, SpellAbility ability, final boolean effect) {
ability.getHostCard().tap(true, ability, payer); Card hostCard = ability.getHostCard();
if (hostCard.tap(true, ability, payer)) {
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
runParams.put(AbilityKey.Cards, new CardCollection(hostCard));
payer.getGame().getTriggerHandler().runTrigger(TriggerType.TapAll, runParams, false);
}
return true; return true;
} }

View File

@@ -18,17 +18,18 @@
package forge.game.cost; package forge.game.cost;
import forge.card.CardType; import forge.card.CardType;
import forge.game.card.Card; import forge.game.ability.AbilityKey;
import forge.game.card.CardCollection; import forge.game.card.*;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.card.CardPredicates.Presets; import forge.game.card.CardPredicates.Presets;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.trigger.TriggerType;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.util.Lang; import forge.util.Lang;
import forge.util.TextUtil; import forge.util.TextUtil;
import java.util.Map;
/** /**
* The Class CostTapType. * The Class CostTapType.
*/ */
@@ -200,6 +201,24 @@ public class CostTapType extends CostPartWithList {
return targetCard; return targetCard;
} }
@Override
protected boolean canPayListAtOnce() {
return true;
}
@Override
protected CardCollectionView doListPayment(Player payer, SpellAbility ability, CardCollectionView targetCards, final boolean effect) {
CardCollection tapped = new CardCollection();
for (Card c : targetCards) {
if (c.tap(true, ability, payer)) tapped.add(c);
}
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
runParams.put(AbilityKey.Cards, tapped);
payer.getGame().getTriggerHandler().runTrigger(TriggerType.TapAll, runParams, false);
return targetCards;
}
/* (non-Javadoc) /* (non-Javadoc)
* @see forge.card.cost.CostPartWithList#getHashForList() * @see forge.card.cost.CostPartWithList#getHashForList()
*/ */

View File

@@ -609,12 +609,18 @@ public class PhaseHandler implements java.io.Serializable {
} while (!success); } while (!success);
CardCollection tapped = new CardCollection();
for (final Card attacker : combat.getAttackers()) { for (final Card attacker : combat.getAttackers()) {
if (!attacker.attackVigilance()) { if (!attacker.attackVigilance()) {
attacker.setTapped(false); attacker.setTapped(false);
attacker.tap(true, true, null, null); if (attacker.tap(true, true, null, null)) tapped.add(attacker);
} }
} }
if (!tapped.isEmpty()) {
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
runParams.put(AbilityKey.Cards, tapped);
whoDeclares.getGame().getTriggerHandler().runTrigger(TriggerType.TapAll, runParams, false);
}
} }
if (game.isGameOver()) { // they just like to close window at any moment if (game.isGameOver()) { // they just like to close window at any moment

View File

@@ -0,0 +1,39 @@
package forge.game.trigger;
import com.google.common.collect.Iterables;
import forge.game.ability.AbilityKey;
import forge.game.card.Card;
import forge.game.card.CardPredicates;
import forge.game.spellability.SpellAbility;
import forge.util.Localizer;
import java.util.Map;
public class TriggerTapAll extends Trigger {
public TriggerTapAll(final Map<String, String> params, final Card host, final boolean intrinsic) {
super(params, host, intrinsic);
}
@Override
public boolean performTest(Map<AbilityKey, Object> runParams) {
return matchesValidParam("ValidCards", runParams.get(AbilityKey.Cards));
}
@Override
public void setTriggeringObjects(SpellAbility sa, Map<AbilityKey, Object> runParams) {
Iterable<Card> cards = (Iterable<Card>) runParams.get(AbilityKey.Cards);
if (hasParam("ValidCards")) {
cards = Iterables.filter(cards, CardPredicates.restriction(getParam("ValidCards").split(","),
getHostCard().getController(), getHostCard(), this));
}
sa.setTriggeringObject(AbilityKey.Cards, cards);
}
@Override
public String getImportantStackObjects(SpellAbility sa) {
return Localizer.getInstance().getMessage("lblTapped") + ": " + sa.getTriggeringObject(AbilityKey.Cards);
}
}

View File

@@ -119,6 +119,7 @@ public enum TriggerType {
SpellCopy(TriggerSpellAbilityCastOrCopy.class), SpellCopy(TriggerSpellAbilityCastOrCopy.class),
Surveil(TriggerSurveil.class), Surveil(TriggerSurveil.class),
TakesInitiative(TriggerTakesInitiative.class), TakesInitiative(TriggerTakesInitiative.class),
TapAll(TriggerTapAll.class),
Taps(TriggerTaps.class), Taps(TriggerTaps.class),
TapsForMana(TriggerTapsForMana.class), TapsForMana(TriggerTapsForMana.class),
TokenCreated(TriggerTokenCreated.class), TokenCreated(TriggerTokenCreated.class),

View File

@@ -4,4 +4,5 @@ Types:Creature Zombie Giant
PT:4/4 PT:4/4
A:AB$ ChangeZone | Cost$ tapXType<3/Cleric> | Origin$ Graveyard | Destination$ Hand | ActivationZone$ Graveyard | SpellDescription$ Return CARDNAME from your graveyard to your hand. A:AB$ ChangeZone | Cost$ tapXType<3/Cleric> | Origin$ Graveyard | Destination$ Hand | ActivationZone$ Graveyard | SpellDescription$ Return CARDNAME from your graveyard to your hand.
AI:RemoveDeck:Random AI:RemoveDeck:Random
DeckNeeds:Type$Cleric
Oracle:Tap three untapped Clerics you control: Return Gangrenous Goliath from your graveyard to your hand. Oracle:Tap three untapped Clerics you control: Return Gangrenous Goliath from your graveyard to your hand.

View File

@@ -0,0 +1,9 @@
Name:Deeproot Pilgrimage
ManaCost:1 U
Types:Enchantment
T:Mode$ TapAll | ValidCards$ Merfolk.nonToken+YouCtrl | TriggerZones$ Battlefield | Execute$ TrigToken | TriggerDescription$ Whenever one or more nontoken Merfolk you control become tapped, create a 1/1 blue Merfolk creature token with hexproof.
SVar:TrigToken:DB$ Token | TokenScript$ u_1_1_merfolk_hexproof
DeckHas:Ability$Token & Type$Merfolk
AI:RemoveDeck:Random
DeckNeeds:Type$Merfolk
Oracle:Whenever one or more nontoken Merfolk you control become tapped, create a 1/1 blue Merfolk creature token with hexproof.

View File

@@ -1,7 +1,7 @@
Name:Succumb to the Cold Name:Succumb to the Cold
ManaCost:2 U ManaCost:2 U
Types:Instant Types:Instant
A:SP$ Tap | ValidTgts$ Creature.OppCtrl | SubAbility$ DBCounter | TargetMin$ 1 | TargetMax$ 2 | TgtPrompt$ Select one or two target creatures an opponent controls | SpellDescription$ Tap one or two target creatures an opponent controls. Put a stun counter on each of them. (If a permanent with a stun counter would become untapped, remove one from it instead.) A:SP$ Tap | ValidTgts$ Creature.OppCtrl | SubAbility$ DBCounter | TargetMin$ 1 | TargetMax$ 2 | TgtPrompt$ Select one or two target creatures an opponent controls | SpellDescription$ Tap one or two target creatures an opponent controls.
SVar:DBCounter:DB$ PutCounter | Defined$ Targeted | CounterType$ Stun | CounterNum$ 1 SVar:DBCounter:DB$ PutCounter | Defined$ Targeted | CounterType$ Stun | CounterNum$ 1 | StackDescription$ REP Put_{p:You} puts | SpellDescription$ Put a stun counter on each of them. (If a permanent with a stun counter would become untapped, remove one from it instead.)
DeckHas:Ability$Counters DeckHas:Ability$Counters
Oracle:Tap one or two target creatures an opponent controls. Put a stun counter on each of them. (If a permanent with a stun counter would become untapped, remove one from it instead.) Oracle:Tap one or two target creatures an opponent controls. Put a stun counter on each of them. (If a permanent with a stun counter would become untapped, remove one from it instead.)

View File

@@ -2,8 +2,10 @@ package forge.player;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map;
import forge.ImageKeys; import forge.ImageKeys;
import forge.game.ability.AbilityKey;
import forge.game.cost.*; import forge.game.cost.*;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
@@ -537,13 +539,19 @@ public class HumanPlay {
} }
if (ability.getTappedForConvoke() != null) { if (ability.getTappedForConvoke() != null) {
game.getTriggerHandler().suppressMode(TriggerType.Taps); game.getTriggerHandler().suppressMode(TriggerType.Taps);
CardCollection tapped = new CardCollection();
for (final Card c : ability.getTappedForConvoke()) { for (final Card c : ability.getTappedForConvoke()) {
c.setTapped(false); c.setTapped(false);
if (!manaInputCancelled) { if (!manaInputCancelled) {
c.tap(true, ability, ability.getActivatingPlayer()); if (c.tap(true, ability, ability.getActivatingPlayer())) tapped.add(c);
} }
} }
game.getTriggerHandler().clearSuppression(TriggerType.Taps); game.getTriggerHandler().clearSuppression(TriggerType.Taps);
if (!tapped.isEmpty()) {
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
runParams.put(AbilityKey.Cards, tapped);
game.getTriggerHandler().runTrigger(TriggerType.TapAll, runParams, false);
}
if (manaInputCancelled) { if (manaInputCancelled) {
ability.clearTappedForConvoke(); ability.clearTappedForConvoke();
} }

View File

@@ -19,6 +19,7 @@ import java.util.Map.Entry;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.TreeSet; import java.util.TreeSet;
import forge.game.trigger.TriggerType;
import forge.trackable.TrackableCollection; import forge.trackable.TrackableCollection;
import forge.util.ImageUtil; import forge.util.ImageUtil;
import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.ObjectUtils;
@@ -2628,8 +2629,14 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
inp.setMessage(localizer.getMessage("lblChoosePermanentstoTap")); inp.setMessage(localizer.getMessage("lblChoosePermanentstoTap"));
inp.showAndWait(); inp.showAndWait();
if (!inp.hasCancelled()) { if (!inp.hasCancelled()) {
CardCollection tapped = new CardCollection();
for (final Card c : inp.getSelected()) { for (final Card c : inp.getSelected()) {
c.tap(true, null, null); if (c.tap(true, null, null)) tapped.add(c);
}
if (!tapped.isEmpty()) {
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
runParams.put(AbilityKey.Cards, tapped);
getGame().getTriggerHandler().runTrigger(TriggerType.TapAll, runParams, false);
} }
} }
}); });