Cycling: add YouCycledThisTurn, OnlyFirst for Trigger and ValidCause for ReplaceDraw

This commit is contained in:
Hans Mackowiak
2020-04-19 03:24:10 +00:00
committed by Michael Kamensky
parent 6b38bc4b7f
commit ef95f02fa2
13 changed files with 145 additions and 32 deletions

View File

@@ -1461,12 +1461,18 @@ public class GameAction {
revealTo(card, Collections.singleton(to));
}
public void revealTo(final CardCollectionView cards, final Player to) {
revealTo(cards, Collections.singleton(to));
revealTo(cards, to, null);
}
public void revealTo(final CardCollectionView cards, final Player to, String messagePrefix) {
revealTo(cards, Collections.singleton(to), messagePrefix);
}
public void revealTo(final Card card, final Iterable<Player> to) {
revealTo(new CardCollection(card), to);
}
public void revealTo(final CardCollectionView cards, final Iterable<Player> to) {
revealTo(cards, to, null);
}
public void revealTo(final CardCollectionView cards, final Iterable<Player> to, String messagePrefix) {
if (cards.isEmpty()) {
return;
}
@@ -1474,7 +1480,7 @@ public class GameAction {
final ZoneType zone = cards.getFirst().getZone().getZoneType();
final Player owner = cards.getFirst().getOwner();
for (final Player p : to) {
p.getController().reveal(cards, zone, owner);
p.getController().reveal(cards, zone, owner, messagePrefix);
}
}

View File

@@ -58,7 +58,7 @@ public class DrawEffect extends SpellAbilityEffect {
actualNum = p.getController().chooseNumber(sa, "lblHowMayCardDoYouWantDraw", 0, numCards);
}
final CardCollectionView drawn = p.drawCards(actualNum);
final CardCollectionView drawn = p.drawCards(actualNum, sa);
if (sa.hasParam("Reveal")) {
p.getGame().getAction().reveal(drawn, p);
}

View File

@@ -909,6 +909,9 @@ public class CardFactoryUtil {
return doXMath(c.getXManaCostPaidCount(colors.toString()), m, c);
}
if (sq[0].equals("YouCycledThisTurn")) {
return doXMath(cc.getCycledThisTurn(), m, c);
}
if (sq[0].equals("YouDrewThisTurn")) {
return doXMath(cc.getNumDrawnThisTurn(), m, c);

View File

@@ -877,6 +877,16 @@ public class CardProperty {
}
}
return false;
default:
final CardCollection cards1 = AbilityUtils.getDefinedCards(card, restriction, spellAbility);
if (cards1.isEmpty()) {
return false;
}
for (Card c : cards1) {
if (!card.sharesCardTypeWith(c)) {
return false;
}
}
}
}
} else if (property.equals("sharesPermanentTypeWith")) {

View File

@@ -96,7 +96,7 @@ public class CostDraw extends CostPart {
@Override
public final boolean payAsDecided(final Player ai, final PaymentDecision decision, SpellAbility ability) {
for (final Player p : getPotentialPlayers(ai, ability.getHostCard())) {
p.drawCards(decision.c);
p.drawCards(decision.c, ability);
}
return true;
}

View File

@@ -93,6 +93,7 @@ public class Player extends GameEntity implements Comparable<Player> {
private int landsPlayedLastTurn = 0;
private int investigatedThisTurn = 0;
private int surveilThisTurn = 0;
private int cycledThisTurn = 0;
private int lifeLostThisTurn = 0;
private int lifeLostLastTurn = 0;
private int lifeGainedThisTurn = 0;
@@ -1269,7 +1270,7 @@ public class Player extends GameEntity implements Comparable<Player> {
}
public final CardCollectionView drawCard() {
return drawCards(1);
return drawCards(1, null);
}
public void surveil(int num, SpellAbility cause) {
@@ -1339,8 +1340,11 @@ public class Player extends GameEntity implements Comparable<Player> {
}
public final CardCollectionView drawCards(final int n) {
return drawCards(n, null);
}
public final CardCollectionView drawCards(final int n, SpellAbility cause) {
final CardCollection drawn = new CardCollection();
final CardCollection toReveal = new CardCollection();
final Map<Player, CardCollection> toReveal = Maps.newHashMap();
// Replacement effects
final Map<AbilityKey, Object> repRunParams = AbilityKey.mapFromAffected(this);
@@ -1357,11 +1361,14 @@ public class Player extends GameEntity implements Comparable<Player> {
if (gameStarted && !canDraw()) {
return drawn;
}
drawn.addAll(doDraw(toReveal));
drawn.addAll(doDraw(toReveal, cause));
}
if (toReveal.size() > 1) {
// reveal multiple drawn cards when playing with the top of the library revealed
game.getAction().reveal(toReveal, this, true, "Revealing cards drawn from ");
// reveal multiple drawn cards when playing with the top of the library revealed
for (Map.Entry<Player, CardCollection> e : toReveal.entrySet()) {
if (e.getValue().size() > 1) {
game.getAction().revealTo(e.getValue(), e.getKey(), "Revealing cards drawn from ");
}
}
return drawn;
}
@@ -1369,31 +1376,36 @@ public class Player extends GameEntity implements Comparable<Player> {
/**
* @return a CardCollectionView of cards actually drawn
*/
private CardCollectionView doDraw(CardCollection revealed) {
private CardCollectionView doDraw(Map<Player, CardCollection> revealed, SpellAbility cause) {
final CardCollection drawn = new CardCollection();
final PlayerZone library = getZone(ZoneType.Library);
// Replacement effects
if (game.getReplacementHandler().run(ReplacementType.Draw, AbilityKey.mapFromAffected(this)) != ReplacementResult.NotReplaced) {
Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(this);
repParams.put(AbilityKey.Cause, cause);
if (game.getReplacementHandler().run(ReplacementType.Draw, repParams) != ReplacementResult.NotReplaced) {
return drawn;
}
if (!library.isEmpty()) {
Card c = library.get(0);
boolean topCardRevealed = false;
for (Player p : this.getAllOtherPlayers()) {
List<Player> pList = Lists.newArrayList();
for (Player p : getAllOtherPlayers()) {
if (c.mayPlayerLook(p)) {
topCardRevealed = true;
break;
pList.add(p);
}
}
c = game.getAction().moveToHand(c, null);
c = game.getAction().moveToHand(c, cause);
drawn.add(c);
if (topCardRevealed) {
revealed.add(c);
for(Player p : pList) {
if (!revealed.containsKey(p)) {
revealed.put(p, new CardCollection());
}
revealed.get(p).add(c);
}
setLastDrawnCard(c);
@@ -2454,6 +2466,7 @@ public class Player extends GameEntity implements Comparable<Player> {
resetLandsPlayedThisTurn();
resetInvestigatedThisTurn();
resetSurveilThisTurn();
resetCycledThisTurn();
resetSacrificedThisTurn();
resetCounterToPermThisTurn();
clearAssignedDamage();
@@ -3157,4 +3170,22 @@ public class Player extends GameEntity implements Comparable<Player> {
}
return controlVotes.last();
}
public void addCycled(SpellAbility sp) {
cycledThisTurn++;
Map<AbilityKey, Object> cycleParams = AbilityKey.mapFromCard(sp.getHostCard());
cycleParams.put(AbilityKey.Cause, sp);
cycleParams.put(AbilityKey.Player, this);
cycleParams.put(AbilityKey.NumThisTurn, cycledThisTurn);
game.getTriggerHandler().runTrigger(TriggerType.Cycled, cycleParams, false);
}
public int getCycledThisTurn() {
return cycledThisTurn;
}
public void resetCycledThisTurn() {
cycledThisTurn = 0;
}
}

View File

@@ -6,17 +6,18 @@
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.game.replacement;
import forge.game.Game;
import forge.game.ability.AbilityKey;
import forge.game.card.Card;
import forge.game.phase.PhaseType;
@@ -25,7 +26,7 @@ import forge.game.spellability.SpellAbility;
import java.util.Map;
/**
/**
* TODO: Write javadoc for this type.
*
*/
@@ -46,16 +47,29 @@ public class ReplaceDraw extends ReplacementEffect {
*/
@Override
public boolean canReplace(Map<AbilityKey, Object> runParams) {
final Game game = this.getHostCard().getGame();
if (hasParam("ValidPlayer")) {
if (!matchesValid(runParams.get(AbilityKey.Affected), getParam("ValidPlayer").split(","), this.getHostCard())) {
if (!matchesValid(runParams.get(AbilityKey.Affected), getParam("ValidPlayer").split(","), getHostCard())) {
return false;
}
}
if (hasParam("ValidCause")) {
if (!runParams.containsKey(AbilityKey.Cause)) {
return false;
}
SpellAbility cause = (SpellAbility) runParams.get(AbilityKey.Cause);
if (cause == null) {
return false;
}
if (!matchesValid(cause, getParam("ValidCause").split(","), getHostCard())) {
return false;
}
}
if (hasParam("NotFirstCardInDrawStep")) {
final Player p = (Player)runParams.get(AbilityKey.Affected);
if (p.numDrawnThisDrawStep() == 0
&& this.getHostCard().getGame().getPhaseHandler().is(PhaseType.DRAW)
&& this.getHostCard().getGame().getPhaseHandler().isPlayerTurn(p)) {
if (p.numDrawnThisDrawStep() == 0 && game.getPhaseHandler().is(PhaseType.DRAW, p)) {
return false;
}
}
@@ -71,5 +85,12 @@ public class ReplaceDraw extends ReplacementEffect {
@Override
public void setReplacingObjects(Map<AbilityKey, Object> runParams, SpellAbility sa) {
sa.setReplacingObject(AbilityKey.Player, runParams.get(AbilityKey.Affected));
if (runParams.containsKey(AbilityKey.Cause)) {
SpellAbility cause = (SpellAbility) runParams.get(AbilityKey.Cause);
if (cause != null) {
sa.setReplacingObject(AbilityKey.Cause, cause);
sa.setReplacingObject(AbilityKey.Source, cause.getHostCard());
}
}
}
}

View File

@@ -19,6 +19,7 @@ package forge.game.trigger;
import forge.game.ability.AbilityKey;
import forge.game.card.Card;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.util.Localizer;
@@ -68,8 +69,22 @@ public class TriggerCycled extends Trigger {
@Override
public final boolean performTest(final Map<AbilityKey, Object> runParams) {
if (hasParam("ValidCard")) {
return matchesValid(runParams.get(AbilityKey.Card), getParam("ValidCard").split(","),
this.getHostCard());
if (!matchesValid(runParams.get(AbilityKey.Card), getParam("ValidCard").split(","), getHostCard())) {
return false;
}
}
if (hasParam("ValidPlayer")) {
Player p = (Player) runParams.get(AbilityKey.Player);
if (!matchesValid(p, getParam("ValidPlayer").split(","), getHostCard())) {
return false;
}
}
if (hasParam("OnlyFirst")) {
if ((int) runParams.get(AbilityKey.NumThisTurn) != 1) {
return false;
}
}
return true;
}

View File

@@ -314,9 +314,7 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
// Run Cycled triggers
if (sp.isCycling()) {
Map<AbilityKey, Object> cycleParams = AbilityKey.mapFromCard(sp.getHostCard());
cycleParams.put(AbilityKey.Cause, sp);
game.getTriggerHandler().runTrigger(TriggerType.Cycled, cycleParams, false);
activator.addCycled(sp);
}
if (sp.hasParam("Crew")) {

View File

@@ -0,0 +1,11 @@
Name:Spellpyre Phoenix
ManaCost:3 R R
Types:Creature Phoenix
PT:4/2
K:Flying
T:Mode$ ChangesZone | ValidCard$ Card.Self | Origin$ Any | Destination$ Battlefield | Execute$ TrigChangeZone | TriggerDescription$ When CARDNAME enters the battlefield, you may return target instant or sorcery card with a cycling ability from your graveyard to your hand.
SVar:TrigChangeZone:DB$ ChangeZone | Origin$ Graveyard | Destination$ Hand | ValidTgts$ Instant.YouOwn+withCycling,Instant.YouOwn+withTypeCycling,Sorcery.YouOwn+withCycling,Sorcery.YouOwn+withTypeCycling | TgtPrompt$ Select target instant or sorcery card with a cycling ability from your graveyard
T:Mode$ Phase | Phase$ End of Turn | TriggerZones$ Graveyard | CheckSVar$ YouCycled | SVarCompare$ GE2 | Execute$ TrigReturn | TriggerDescription$ At the beginning of each end step, if you cycled two or more cards this turn, return CARDNAME from your graveyard to your hand.
SVar:TrigReturn:DB$ ChangeZone | Defined$ Self | Origin$ Graveyard | Destination$ Hand
SVar:YouCycled:Count$YouCycledThisTurn
Oracle:Flying\nWhen Spellpyre Phoenix enters the battlefield, you may return target instant or sorcery card with a cycling ability from your graveyard to your hand.\nAt the beginning of each end step, if you cycled two or more cards this turn, return Spellpyre Phoenix from your graveyard to your hand.

View File

@@ -0,0 +1,10 @@
Name:Unpredictable Cyclone
ManaCost:3 R R
Types:Enchantment
K:Cycling:2
R:Event$ Draw | ValidCause$ Activated.Cycling+nonLand | ValidPlayer$ You | ActiveZones$ Battlefield | ReplaceWith$ DBDig | Description$ If a cycling ability of another nonland card would cause you to draw a card, instead exile cards from the top of your library until you exile a card that shares a card type with the cycled card. You may cast that card without paying its mana cost. Then put the exiled cards that weren't cast this way on the bottom of your library in a random order.
SVar:DBDig:DB$ DigUntil | Defined$ You | Valid$ Card.sharesCardTypeWith ReplacedSource | ValidDescription$ shares a card type with exiled card | FoundDestination$ Exile | RevealedDestination$ Exile | RememberFound$ True | RememberRevealed$ True | SubAbility$ DBPlay
SVar:DBPlay:DB$ Play | Defined$ Remembered.nonLand+sharesCardTypeWith ReplacedSource | WithoutManaCost$ True | Optional$ True | ForgetTargetRemembered$ True | SubAbility$ DBRestRandomOrder
SVar:DBRestRandomOrder:DB$ ChangeZone | Defined$ Remembered | AtRandom$ True | Origin$ Library | Destination$ Library | LibraryPosition$ -1 | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
Oracle:If a cycling ability of another nonland card would cause you to draw a card, instead exile cards from the top of your library until you exile a card that shares a card type with the cycled card. You may cast that card without paying its mana cost. Then put the exiled cards that weren't cast this way on the bottom of your library in a random order.\nCycling {2} ({2}, Discard this card: Draw a card.)

View File

@@ -0,0 +1,8 @@
Name:Valiant Rescuer
ManaCost:1 W
Types:Creature Human Soldier
PT:3/1
T:Mode$ Cycled | ValidCard$ Card.Other | ValidPlayer$ You | TriggerZones$ Battlefield | OnlyFirst$ True | Execute$ TrigToken | TriggerDescription$ Whenever you cycle another card for the first time each turn, create a 1/1 white Human Soldier creature token.
SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ w_1_1_human_soldier | TokenOwner$ You | LegacyImage$ w 1 1 human soldier iko
K:Cycling:2
Oracle:Whenever you cycle another card for the first time each turn, create a 1/1 white Human Soldier creature token.\nCycling {2} ({2}, Discard this card: Draw a card.)

View File

@@ -345,7 +345,7 @@ public class HumanPlay {
}
for (Player player : res) {
player.drawCards(amount);
player.drawCards(amount, sourceAbility);
}
}
else if (part instanceof CostGainLife) {