mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-15 18:28:00 +00:00
Cycling: add YouCycledThisTurn, OnlyFirst for Trigger and ValidCause for ReplaceDraw
This commit is contained in:
committed by
Michael Kamensky
parent
6b38bc4b7f
commit
ef95f02fa2
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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")) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
*/
|
||||
package forge.game.replacement;
|
||||
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.AbilityKey;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.phase.PhaseType;
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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")) {
|
||||
|
||||
@@ -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.
|
||||
10
forge-gui/res/cardsfolder/upcoming/unpredictable_cyclone.txt
Normal file
10
forge-gui/res/cardsfolder/upcoming/unpredictable_cyclone.txt
Normal 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.)
|
||||
8
forge-gui/res/cardsfolder/upcoming/valiant_rescuer.txt
Normal file
8
forge-gui/res/cardsfolder/upcoming/valiant_rescuer.txt
Normal 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.)
|
||||
@@ -345,7 +345,7 @@ public class HumanPlay {
|
||||
}
|
||||
|
||||
for (Player player : res) {
|
||||
player.drawCards(amount);
|
||||
player.drawCards(amount, sourceAbility);
|
||||
}
|
||||
}
|
||||
else if (part instanceof CostGainLife) {
|
||||
|
||||
Reference in New Issue
Block a user