Codechanges and lots of fixes related to playing other player's cards from exile.

- Cards now keep track of who's allowed to cast them, fixing possible issues in multiplayer.
- Face-down cards can now be properly looked at when allowed to by a static ability.
- Improve support for casting a face-down exiled card in general.
- Add Shared Fate (including AI support).
This commit is contained in:
elcnesh
2014-12-03 08:49:49 +00:00
parent 4ae2687478
commit ea23bb33b2
28 changed files with 251 additions and 93 deletions

2
.gitattributes vendored
View File

@@ -446,6 +446,7 @@ forge-game/src/main/java/forge/game/card/CardDamageHistory.java -text
forge-game/src/main/java/forge/game/card/CardFactory.java svneol=native#text/plain forge-game/src/main/java/forge/game/card/CardFactory.java svneol=native#text/plain
forge-game/src/main/java/forge/game/card/CardFactoryUtil.java svneol=native#text/plain forge-game/src/main/java/forge/game/card/CardFactoryUtil.java svneol=native#text/plain
forge-game/src/main/java/forge/game/card/CardLists.java svneol=native#text/plain forge-game/src/main/java/forge/game/card/CardLists.java svneol=native#text/plain
forge-game/src/main/java/forge/game/card/CardPlayOption.java -text
forge-game/src/main/java/forge/game/card/CardPowerToughness.java svneol=native#text/plain forge-game/src/main/java/forge/game/card/CardPowerToughness.java svneol=native#text/plain
forge-game/src/main/java/forge/game/card/CardPredicates.java svneol=native#text/plain forge-game/src/main/java/forge/game/card/CardPredicates.java svneol=native#text/plain
forge-game/src/main/java/forge/game/card/CardShields.java -text forge-game/src/main/java/forge/game/card/CardShields.java -text
@@ -12181,6 +12182,7 @@ forge-gui/res/cardsfolder/s/sharding_sphinx.txt svneol=native#text/plain
forge-gui/res/cardsfolder/s/shardless_agent.txt -text forge-gui/res/cardsfolder/s/shardless_agent.txt -text
forge-gui/res/cardsfolder/s/shared_animosity.txt -text forge-gui/res/cardsfolder/s/shared_animosity.txt -text
forge-gui/res/cardsfolder/s/shared_discovery.txt svneol=native#text/plain forge-gui/res/cardsfolder/s/shared_discovery.txt svneol=native#text/plain
forge-gui/res/cardsfolder/s/shared_fate.txt -text
forge-gui/res/cardsfolder/s/shared_trauma.txt -text forge-gui/res/cardsfolder/s/shared_trauma.txt -text
forge-gui/res/cardsfolder/s/shared_triumph.txt svneol=native#text/plain forge-gui/res/cardsfolder/s/shared_triumph.txt svneol=native#text/plain
forge-gui/res/cardsfolder/s/sharpened_pitchfork.txt -text forge-gui/res/cardsfolder/s/sharpened_pitchfork.txt -text

View File

@@ -34,6 +34,7 @@ import com.google.common.base.Predicate;
import com.google.common.base.Predicates; import com.google.common.base.Predicates;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import forge.card.CardStateName;
import forge.card.CardType; import forge.card.CardType;
import forge.card.MagicColor; import forge.card.MagicColor;
import forge.card.CardType.Supertype; import forge.card.CardType.Supertype;
@@ -335,7 +336,7 @@ public class AiController {
sa.setActivatingPlayer(player); sa.setActivatingPlayer(player);
//add alternative costs as additional spell abilities //add alternative costs as additional spell abilities
newAbilities.add(sa); newAbilities.add(sa);
newAbilities.addAll(GameActionUtil.getAlternativeCosts(sa)); newAbilities.addAll(GameActionUtil.getAlternativeCosts(sa, player));
} }
final ArrayList<SpellAbility> result = new ArrayList<SpellAbility>(); final ArrayList<SpellAbility> result = new ArrayList<SpellAbility>();
@@ -352,6 +353,11 @@ public class AiController {
for (final SpellAbility sa : c.getSpellAbilities()) { for (final SpellAbility sa : c.getSpellAbilities()) {
spellAbilities.add(sa); spellAbilities.add(sa);
} }
if (c.isFaceDown() && c.isInZone(ZoneType.Exile) && c.mayPlay(player) != null) {
for (final SpellAbility sa : c.getState(CardStateName.Original).getSpellAbilities()) {
spellAbilities.add(sa);
}
}
} }
return spellAbilities; return spellAbilities;
} }
@@ -392,11 +398,15 @@ public class AiController {
}); });
final CardCollection landsNotInHand = new CardCollection(player.getCardsIn(ZoneType.Graveyard)); final CardCollection landsNotInHand = new CardCollection(player.getCardsIn(ZoneType.Graveyard));
landsNotInHand.addAll(game.getCardsIn(ZoneType.Exile));
if (!player.getCardsIn(ZoneType.Library).isEmpty()) { if (!player.getCardsIn(ZoneType.Library).isEmpty()) {
landsNotInHand.add(player.getCardsIn(ZoneType.Library).get(0)); landsNotInHand.add(player.getCardsIn(ZoneType.Library).get(0));
} }
for (final Card crd : landsNotInHand) { for (final Card crd : landsNotInHand) {
if (crd.isLand() && crd.hasKeyword("May be played")) { if (!(crd.isLand() || (crd.isFaceDown() && crd.getState(CardStateName.Original).getType().isLand()))) {
continue;
}
if (crd.hasKeyword("May be played") || crd.mayPlay(player) != null) {
landList.add(crd); landList.add(crd);
} }
} }

View File

@@ -29,6 +29,7 @@ import forge.game.ability.ApiType;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardCollectionView; import forge.game.card.CardCollectionView;
import forge.game.card.CardLists; import forge.game.card.CardLists;
import forge.game.card.CardPlayOption;
import forge.game.card.CardPredicates; import forge.game.card.CardPredicates;
import forge.game.cost.Cost; import forge.game.cost.Cost;
import forge.game.mana.ManaCostBeingPaid; import forge.game.mana.ManaCostBeingPaid;
@@ -143,20 +144,37 @@ public final class GameActionUtil {
/** /**
* <p> * <p>
* getAlternativeCosts. * Find the alternative costs to a {@link SpellAbility}.
* </p> * </p>
* *
* @param sa * @param sa
* a SpellAbility. * a {@link SpellAbility}.
* @return an ArrayList<SpellAbility>. * @param activator
* get alternative costs as additional spell abilities * the {@link Player} for which to calculate available
* @return a {@link List} of {@link SpellAbility} objects, each representing
* a possible alternative cost the provided activator can use to pay
* the provided {@link SpellAbility}.
*/ */
public static final ArrayList<SpellAbility> getAlternativeCosts(SpellAbility sa) { public static final List<SpellAbility> getAlternativeCosts(final SpellAbility sa, final Player activator) {
ArrayList<SpellAbility> alternatives = new ArrayList<SpellAbility>(); final List<SpellAbility> alternatives = new ArrayList<SpellAbility>();
Card source = sa.getHostCard();
if (!sa.isBasicSpell()) { if (!sa.isBasicSpell()) {
return alternatives; return alternatives;
} }
final Card source = sa.getHostCard();
final CardPlayOption playOption = source.mayPlay(activator);
if (sa.isSpell() && playOption != null && playOption.isWithoutManaCost()) {
final SpellAbility newSA = sa.copy();
final SpellAbilityRestriction sar = new SpellAbilityRestriction();
sar.setVariables(sa.getRestrictions());
sar.setZone(null);
newSA.setRestrictions(sar);
newSA.setBasicSpell(false);
newSA.setPayCosts(newSA.getPayCosts().copyWithNoMana());
newSA.setDescription(sa.getDescription() + " (without paying its mana cost)");
alternatives.add(newSA);
}
for (final String keyword : source.getKeywords()) { for (final String keyword : source.getKeywords()) {
if (sa.isSpell() && keyword.startsWith("Flashback")) { if (sa.isSpell() && keyword.startsWith("Flashback")) {
final SpellAbility flashback = sa.copy(); final SpellAbility flashback = sa.copy();
@@ -183,18 +201,6 @@ public final class GameActionUtil {
newSA.setDescription(sa.getDescription() + " (without paying its mana cost)"); newSA.setDescription(sa.getDescription() + " (without paying its mana cost)");
alternatives.add(newSA); alternatives.add(newSA);
} }
if (sa.isSpell() && keyword.equals("May be played by your opponent without paying its mana cost")) {
final SpellAbility newSA = sa.copy();
SpellAbilityRestriction sar = new SpellAbilityRestriction();
sar.setVariables(sa.getRestrictions());
sar.setZone(null);
sar.setOpponentOnly(true);
newSA.setRestrictions(sar);
newSA.setBasicSpell(false);
newSA.setPayCosts(newSA.getPayCosts().copyWithNoMana());
newSA.setDescription(sa.getDescription() + " (without paying its mana cost)");
alternatives.add(newSA);
}
if (sa.isSpell() && keyword.startsWith("May be played without paying its mana cost and as though it has flash")) { if (sa.isSpell() && keyword.startsWith("May be played without paying its mana cost and as though it has flash")) {
final SpellAbility newSA = sa.copy(); final SpellAbility newSA = sa.copy();
SpellAbilityRestriction sar = new SpellAbilityRestriction(); SpellAbilityRestriction sar = new SpellAbilityRestriction();

View File

@@ -92,7 +92,7 @@ public class StaticEffects {
boolean setPT = false; boolean setPT = false;
String[] addHiddenKeywords = null; String[] addHiddenKeywords = null;
String addColors = null; String addColors = null;
boolean removeMayLookAt = false; boolean removeMayLookAt = false, removeMayPlay = false;
if (params.containsKey("ChangeColorWordsTo")) { if (params.containsKey("ChangeColorWordsTo")) {
changeColorWordsTo = params.get("ChangeColorWordsTo"); changeColorWordsTo = params.get("ChangeColorWordsTo");
@@ -158,6 +158,9 @@ public class StaticEffects {
if (params.containsKey("MayLookAt")) { if (params.containsKey("MayLookAt")) {
removeMayLookAt = true; removeMayLookAt = true;
} }
if (params.containsKey("MayPlay")) {
removeMayPlay = true;
}
if (params.containsKey("IgnoreEffectCost")) { if (params.containsKey("IgnoreEffectCost")) {
for (final SpellAbility s : se.getSource().getSpellAbilities()) { for (final SpellAbility s : se.getSource().getSpellAbilities()) {
@@ -254,6 +257,9 @@ public class StaticEffects {
if (removeMayLookAt) { if (removeMayLookAt) {
affectedCard.setMayLookAt(controller, false); affectedCard.setMayLookAt(controller, false);
} }
if (removeMayPlay) {
affectedCard.removeMayPlay(controller);
}
} }
se.clearTimestamps(); se.clearTimestamps();
return affectedCards; return affectedCards;

View File

@@ -112,8 +112,6 @@ public class EffectEffect extends SpellAbilityEffect {
eff.setImmutable(true); eff.setImmutable(true);
eff.setEffectSource(hostCard); eff.setEffectSource(hostCard);
// Effects should be Orange or something probably
final Card e = eff; final Card e = eff;
// Grant SVars first in order to give references to granted abilities // Grant SVars first in order to give references to granted abilities

View File

@@ -136,6 +136,8 @@ public class Card extends GameEntity implements Comparable<Card>, IIdentifiable
// if this card is an Aura, what Entity is it enchanting? // if this card is an Aura, what Entity is it enchanting?
private GameEntity enchanting = null; private GameEntity enchanting = null;
private final Map<Player, CardPlayOption> mayPlay = Maps.newTreeMap();
// changes by AF animate and continuous static effects - timestamp is the key of maps // changes by AF animate and continuous static effects - timestamp is the key of maps
private Map<Long, CardChangedType> changedCardTypes = new ConcurrentSkipListMap<Long, CardChangedType>(); private Map<Long, CardChangedType> changedCardTypes = new ConcurrentSkipListMap<Long, CardChangedType>();
private Map<Long, KeywordsChange> changedCardKeywords = new ConcurrentSkipListMap<Long, KeywordsChange>(); private Map<Long, KeywordsChange> changedCardKeywords = new ConcurrentSkipListMap<Long, KeywordsChange>();
@@ -1503,10 +1505,22 @@ public class Card extends GameEntity implements Comparable<Card>, IIdentifiable
public String getAbilityText() { public String getAbilityText() {
return getAbilityText(currentState); return getAbilityText(currentState);
} }
public String getAbilityText(CardState state) { public String getAbilityText(final CardState state) {
final CardTypeView type = state.getType(); final CardTypeView type = state.getType();
final StringBuilder sb = new StringBuilder();
if (!mayPlay.isEmpty()) {
sb.append("May be played by: ");
sb.append(Lang.joinHomogenous(mayPlay.entrySet(), new Function<Entry<Player, CardPlayOption>, String>() {
@Override public String apply(final Entry<Player, CardPlayOption> entry) {
return entry.getKey().toString() + entry.getValue().toString();
}
}));
sb.append("\r\n");
}
if (type.isInstant() || type.isSorcery()) { if (type.isInstant() || type.isSorcery()) {
final StringBuilder sb = abilityTextInstantSorcery(state); sb.append(abilityTextInstantSorcery(state));
if (haunting != null) { if (haunting != null) {
sb.append("Haunting: ").append(haunting); sb.append("Haunting: ").append(haunting);
@@ -1520,8 +1534,6 @@ public class Card extends GameEntity implements Comparable<Card>, IIdentifiable
return sb.toString().replaceAll("CARDNAME", state.getName()); return sb.toString().replaceAll("CARDNAME", state.getName());
} }
final StringBuilder sb = new StringBuilder();
if (monstrous) { if (monstrous) {
sb.append("Monstrous\r\n"); sb.append("Monstrous\r\n");
} }
@@ -2141,6 +2153,17 @@ public class Card extends GameEntity implements Comparable<Card>, IIdentifiable
view.setPlayerMayLook(player, mayLookAt, temp); view.setPlayerMayLook(player, mayLookAt, temp);
} }
public final CardPlayOption mayPlay(final Player player) {
return mayPlay.get(player);
}
public final void setMayPlay(final Player player, final boolean withoutManaCost, final boolean ignoreColor) {
final CardPlayOption option = this.mayPlay.get(player);
this.mayPlay.put(player, option == null ? new CardPlayOption(withoutManaCost, ignoreColor) : option.add(withoutManaCost, ignoreColor));
}
public final void removeMayPlay(final Player player) {
this.mayPlay.remove(player);
}
public final CardCollectionView getEquippedBy(boolean allowModify) { public final CardCollectionView getEquippedBy(boolean allowModify) {
return CardCollection.getView(equippedBy, allowModify); return CardCollection.getView(equippedBy, allowModify);
} }
@@ -6246,13 +6269,18 @@ public class Card extends GameEntity implements Comparable<Card>, IIdentifiable
return null; return null;
} }
public List<SpellAbility> getAllPossibleAbilities(Player player, boolean removeUnplayable) { public List<SpellAbility> getAllPossibleAbilities(final Player player, final boolean removeUnplayable) {
// this can only be called by the Human // this can only be called by the Human
final List<SpellAbility> abilities = new ArrayList<SpellAbility>(); final List<SpellAbility> abilities = new ArrayList<SpellAbility>();
for (SpellAbility sa : getSpellAbilities()) { for (SpellAbility sa : getSpellAbilities()) {
//add alternative costs as additional spell abilities //add alternative costs as additional spell abilities
abilities.add(sa); abilities.add(sa);
abilities.addAll(GameActionUtil.getAlternativeCosts(sa)); abilities.addAll(GameActionUtil.getAlternativeCosts(sa, player));
}
if (isFaceDown() && isInZone(ZoneType.Exile) && mayPlay(player) != null) {
for (final SpellAbility sa : getState(CardStateName.Original).getSpellAbilities()) {
abilities.add(sa);
}
} }
for (int i = abilities.size() - 1; i >= 0; i--) { for (int i = abilities.size() - 1; i >= 0; i--) {
@@ -6266,7 +6294,7 @@ public class Card extends GameEntity implements Comparable<Card>, IIdentifiable
} }
} }
if (isLand() && player.canPlayLand(this)) { if (getState(CardStateName.Original).getType().isLand() && player.canPlayLand(this)) {
Ability.PLAY_LAND_SURROGATE.setHostCard(this); Ability.PLAY_LAND_SURROGATE.setHostCard(this);
abilities.add(Ability.PLAY_LAND_SURROGATE); abilities.add(Ability.PLAY_LAND_SURROGATE);
} }

View File

@@ -0,0 +1,36 @@
package forge.game.card;
import org.apache.commons.lang3.StringUtils;
public final class CardPlayOption {
private final boolean withoutManaCost, ignoreManaCostColor;
public CardPlayOption(final boolean withoutManaCost, final boolean ignoreManaCostColor) {
this.withoutManaCost = withoutManaCost;
this.ignoreManaCostColor = ignoreManaCostColor;
}
public CardPlayOption add(final boolean withoutManaCost, final boolean ignoreManaCostColor) {
return new CardPlayOption(isWithoutManaCost() || withoutManaCost, isIgnoreManaCostColor() || ignoreManaCostColor);
}
public boolean isWithoutManaCost() {
return withoutManaCost;
}
public boolean isIgnoreManaCostColor() {
return ignoreManaCostColor;
}
@Override
public String toString() {
if (isWithoutManaCost()) {
return " (without paying its mana cost)";
}
if (isIgnoreManaCostColor()) {
return " (may spend mana as though it were mana of any color to cast it)";
}
return StringUtils.EMPTY;
}
}

View File

@@ -1,9 +1,12 @@
package forge.game.card; package forge.game.card;
import java.util.EnumSet;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import com.google.common.collect.Iterables;
import forge.ImageKeys; import forge.ImageKeys;
import forge.card.CardStateName; import forge.card.CardStateName;
import forge.card.CardEdition; import forge.card.CardEdition;
@@ -102,6 +105,9 @@ public class CardView extends GameEntityView {
void updateZone(Card c) { void updateZone(Card c) {
set(TrackableProperty.Zone, c.getZone() == null ? null : c.getZone().getZoneType()); set(TrackableProperty.Zone, c.getZone() == null ? null : c.getZone().getZoneType());
} }
public boolean isInZone(final Iterable<ZoneType> zones) {
return Iterables.contains(zones, getZone());
}
public boolean isCloned() { public boolean isCloned() {
return get(TrackableProperty.Cloned); return get(TrackableProperty.Cloned);
@@ -397,11 +403,17 @@ public class CardView extends GameEntityView {
} }
//if viewer is controlled by another player, also check if face can be shown to that player //if viewer is controlled by another player, also check if face can be shown to that player
PlayerView mindSlaveMaster = viewer.getMindSlaveMaster(); final PlayerView mindSlaveMaster = viewer.getMindSlaveMaster();
if (mindSlaveMaster != null && canFaceDownBeShownTo(mindSlaveMaster)) { if (mindSlaveMaster != null && canFaceDownBeShownTo(mindSlaveMaster)) {
return true; return true;
} }
return !getController().isOpponentOf(viewer) || getCurrentState().getOpponentMayLook(); if (isInZone(EnumSet.of(ZoneType.Battlefield, ZoneType.Stack, ZoneType.Sideboard)) && getController().equals(viewer)) {
return true;
}
if (getController().isOpponentOf(viewer) && getCurrentState().getOpponentMayLook()) {
return true;
}
return false;
} }
public CardView getEquipping() { public CardView getEquipping() {

View File

@@ -1386,6 +1386,9 @@ public class Player extends GameEntity implements Comparable<Player> {
// Dakkon Blackblade Avatar will use a similar effect // Dakkon Blackblade Avatar will use a similar effect
if (canPlayLand(land, ignoreZoneAndTiming)) { if (canPlayLand(land, ignoreZoneAndTiming)) {
land.setController(this, 0); land.setController(this, 0);
if (land.isFaceDown()) {
land.turnFaceUp();
}
game.getAction().moveTo(getZone(ZoneType.Battlefield), land); game.getAction().moveTo(getZone(ZoneType.Battlefield), land);
// play a sound // play a sound
@@ -1407,7 +1410,7 @@ public class Player extends GameEntity implements Comparable<Player> {
public final boolean canPlayLand(final Card land) { public final boolean canPlayLand(final Card land) {
return canPlayLand(land, false); return canPlayLand(land, false);
} }
public final boolean canPlayLand(Card land, final boolean ignoreZoneAndTiming) { public final boolean canPlayLand(final Card land, final boolean ignoreZoneAndTiming) {
if (!ignoreZoneAndTiming && !canCastSorcery()) { if (!ignoreZoneAndTiming && !canCastSorcery()) {
return false; return false;
} }
@@ -1423,14 +1426,13 @@ public class Player extends GameEntity implements Comparable<Player> {
} }
if (land != null && !ignoreZoneAndTiming) { if (land != null && !ignoreZoneAndTiming) {
if (land.getOwner() != this && !land.hasKeyword("May be played by your opponent")) final boolean mayPlay = land.mayPlay(this) != null;
return false; if (land.getOwner() != this && !mayPlay) {
if (land.getOwner() == this && land.hasKeyword("May be played by your opponent") && !land.hasKeyword("May be played"))
return false; return false;
}
final Zone zone = game.getZoneOf(land); final Zone zone = game.getZoneOf(land);
if (zone != null && (zone.is(ZoneType.Battlefield) || (!zone.is(ZoneType.Hand) && !land.hasStartOfKeyword("May be played")))) { if (zone != null && (zone.is(ZoneType.Battlefield) || (!zone.is(ZoneType.Hand) && !(mayPlay || land.hasStartOfKeyword("May be played"))))) {
return false; return false;
} }
} }

View File

@@ -17,6 +17,7 @@
*/ */
package forge.game.spellability; package forge.game.spellability;
import forge.card.CardStateName;
import forge.game.Game; import forge.game.Game;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardCollection; import forge.game.card.CardCollection;
@@ -91,7 +92,7 @@ public abstract class Spell extends SpellAbility implements java.io.Serializable
} }
// for uncastables like lotus bloom, check if manaCost is blank (except for morph spells) // for uncastables like lotus bloom, check if manaCost is blank (except for morph spells)
if (!isCastFaceDown() && isBasicSpell() && card.getManaCost().isNoCost()) { if (!isCastFaceDown() && isBasicSpell() && card.getState(card.isFaceDown() ? CardStateName.Original : card.getCurrentStateName()).getManaCost().isNoCost()) {
return false; return false;
} }

View File

@@ -200,9 +200,8 @@ public class SpellAbilityRestriction extends SpellAbilityVariables {
return true; return true;
} }
Player activator = sa.getActivatingPlayer(); final Player activator = sa.getActivatingPlayer();
final Zone cardZone = activator.getGame().getZoneOf(c);
Zone cardZone = activator.getGame().getZoneOf(c);
if (cardZone == null || !cardZone.is(this.getZone())) { if (cardZone == null || !cardZone.is(this.getZone())) {
// If Card is not in the default activating zone, do some additional checks // If Card is not in the default activating zone, do some additional checks
// Not a Spell, or on Battlefield, return false // Not a Spell, or on Battlefield, return false
@@ -213,10 +212,10 @@ public class SpellAbilityRestriction extends SpellAbilityVariables {
if (cardZone.is(ZoneType.Stack)) { if (cardZone.is(ZoneType.Stack)) {
return false; return false;
} }
if (c.hasKeyword("May be played") && activator.equals(c.getController())) { if (c.mayPlay(activator) != null) {
return true; return true;
} }
if (c.hasKeyword("May be played by your opponent") && !activator.equals(c.getController())) { if (c.hasKeyword("May be played") && activator.equals(c.getController())) {
return true; return true;
} }
return false; return false;
@@ -296,7 +295,7 @@ public class SpellAbilityRestriction extends SpellAbilityVariables {
return true; return true;
} }
if (sa.isSpell() && activator.isOpponentOf(c.getController()) && c.hasKeyword("May be played by your opponent")) { if (sa.isSpell() && c.mayPlay(activator) != null) {
return true; return true;
} }

View File

@@ -257,9 +257,8 @@ public class StaticAbility extends CardTraitBase {
return in; return in;
} }
// apply the ability if it has the right mode
/** /**
* Apply ability. * Apply ability if it has the right mode.
* *
* @param mode * @param mode
* the mode * the mode

View File

@@ -113,6 +113,7 @@ public class StaticAbilityContinuous {
boolean removeSubTypes = false; boolean removeSubTypes = false;
boolean removeCreatureTypes = false; boolean removeCreatureTypes = false;
boolean controllerMayLookAt = false; boolean controllerMayLookAt = false;
boolean controllerMayPlay = false, mayPlayWithoutManaCost = false, mayPlayIgnoreColor = false;
//Global rules changes //Global rules changes
if (params.containsKey("GlobalRule")) { if (params.containsKey("GlobalRule")) {
@@ -328,6 +329,14 @@ public class StaticAbilityContinuous {
if (params.containsKey("MayLookAt")) { if (params.containsKey("MayLookAt")) {
controllerMayLookAt = true; controllerMayLookAt = true;
} }
if (params.containsKey("MayPlay")) {
controllerMayPlay = true;
if (params.containsKey("MayPlayWithoutManaCost")) {
mayPlayWithoutManaCost = true;
} else if (params.containsKey("MayPlayIgnoreColor")) {
mayPlayIgnoreColor = true;
}
}
if (params.containsKey("IgnoreEffectCost")) { if (params.containsKey("IgnoreEffectCost")) {
String cost = params.get("IgnoreEffectCost"); String cost = params.get("IgnoreEffectCost");
@@ -558,6 +567,9 @@ public class StaticAbilityContinuous {
if (controllerMayLookAt) { if (controllerMayLookAt) {
affectedCard.setMayLookAt(controller, true); affectedCard.setMayLookAt(controller, true);
} }
if (controllerMayPlay) {
affectedCard.setMayPlay(controller, mayPlayWithoutManaCost, mayPlayIgnoreColor);
}
} }
return affectedCards; return affectedCards;

View File

@@ -39,14 +39,17 @@ import forge.game.zone.ZoneType;
import java.util.*; import java.util.*;
import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimaps;
public class TriggerHandler { public class TriggerHandler {
private final ArrayList<TriggerType> suppressedModes = new ArrayList<TriggerType>(); private final List<TriggerType> suppressedModes = Collections.synchronizedList(new ArrayList<TriggerType>());
private final ArrayList<Trigger> activeTriggers = new ArrayList<Trigger>(); private final List<Trigger> activeTriggers = Collections.synchronizedList(new ArrayList<Trigger>());
private final ArrayList<Trigger> delayedTriggers = new ArrayList<Trigger>(); private final List<Trigger> delayedTriggers = Collections.synchronizedList(new ArrayList<Trigger>());
private final ArrayListMultimap<Player, Trigger> playerDefinedDelayedTriggers = ArrayListMultimap.create(); private final ListMultimap<Player, Trigger> playerDefinedDelayedTriggers = Multimaps.synchronizedListMultimap(ArrayListMultimap.<Player, Trigger>create());
private final List<TriggerWaiting> waitingTriggers = new ArrayList<TriggerWaiting>(); private final List<TriggerWaiting> waitingTriggers = Collections.synchronizedList(new ArrayList<TriggerWaiting>());
private final Game game; private final Game game;
public TriggerHandler(Game gameState) { public TriggerHandler(Game gameState) {
@@ -268,7 +271,7 @@ public class TriggerHandler {
boolean checkStatics = false; boolean checkStatics = false;
// Static triggers // Static triggers
for (final Trigger t : activeTriggers) { for (final Trigger t : Lists.newArrayList(activeTriggers)) {
if (t.isStatic() && canRunTrigger(t, mode, runParams)) { if (t.isStatic() && canRunTrigger(t, mode, runParams)) {
runSingleTrigger(t, runParams); runSingleTrigger(t, runParams);
checkStatics = true; checkStatics = true;

View File

@@ -216,9 +216,11 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
if (sp.isSpell()) { if (sp.isSpell()) {
source.setController(activator, 0); source.setController(activator, 0);
Spell spell = (Spell) sp; final Spell spell = (Spell) sp;
if (spell.isCastFaceDown()) { if (spell.isCastFaceDown()) {
source.turnFaceDown(); source.turnFaceDown();
} else if (source.isFaceDown()) {
source.turnFaceUp();
} }
} }

View File

@@ -38,8 +38,8 @@ public enum TrackableProperty {
ChosenDirection(TrackableTypes.EnumType(Direction.class)), ChosenDirection(TrackableTypes.EnumType(Direction.class)),
Remembered(TrackableTypes.StringType), Remembered(TrackableTypes.StringType),
NamedCard(TrackableTypes.StringType), NamedCard(TrackableTypes.StringType),
PlayerMayLook(TrackableTypes.PlayerViewCollectionType), PlayerMayLook(TrackableTypes.PlayerViewCollectionType, false),
PlayerMayLookTemp(TrackableTypes.PlayerViewCollectionType), PlayerMayLookTemp(TrackableTypes.PlayerViewCollectionType, false),
Equipping(TrackableTypes.CardViewType), Equipping(TrackableTypes.CardViewType),
EquippedBy(TrackableTypes.CardViewCollectionType), EquippedBy(TrackableTypes.CardViewCollectionType),
Enchanting(TrackableTypes.GameEntityViewType), Enchanting(TrackableTypes.GameEntityViewType),

View File

@@ -7,9 +7,8 @@ T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player | CombatDamage
SVar:TrigExile:AB$ Mill | Cost$ 0 | Defined$ TriggeredTarget | NumCards$ 1 | Destination$ Exile | RememberMilled$ True | SubAbility$ DBGainLife SVar:TrigExile:AB$ Mill | Cost$ 0 | Defined$ TriggeredTarget | NumCards$ 1 | Destination$ Exile | RememberMilled$ True | SubAbility$ DBGainLife
SVar:DBGainLife:DB$ GainLife | Defined$ You | LifeAmount$ X | References$ X | SubAbility$ DBEffect SVar:DBGainLife:DB$ GainLife | Defined$ You | LifeAmount$ X | References$ X | SubAbility$ DBEffect
SVar:X:Remembered$CardManaCost SVar:X:Remembered$CardManaCost
SVar:DBEffect:DB$ Effect | StaticAbilities$ STPlay,STPlay2 | Triggers$ TriggerCastDoM | SVars$ TrigRemoveSelf | RememberObjects$ Remembered | SubAbility$ DBCleanup SVar:DBEffect:DB$ Effect | StaticAbilities$ STPlay | Triggers$ TriggerCastDoM | SVars$ TrigRemoveSelf | RememberObjects$ Remembered | SubAbility$ DBCleanup
SVar:STPlay:Mode$ Continuous | EffectZone$ Command | Affected$ Card.IsRemembered+nonLand+YouOwn | AddHiddenKeyword$ May be played & May spend mana as though it were mana of any color to cast CARDNAME | AffectedZone$ Exile | Description$ Until end of turn, you may cast that card and you may spend mana as though it were mana of any color to cast it. SVar:STPlay:Mode$ Continuous | MayPlay$ True | MayPlayIgnoreColor$ True | EffectZone$ Command | Affected$ Card.IsRemembered+nonLand | AffectedZone$ Exile | Description$ Until end of turn, you may cast that card and you may spend mana as though it were mana of any color to cast it.
SVar:STPlay2:Mode$ Continuous | EffectZone$ Command | Affected$ Card.IsRemembered+nonLand+OppOwn | AddHiddenKeyword$ May be played by your opponent & May spend mana as though it were mana of any color to cast CARDNAME | AffectedZone$ Exile
SVar:TriggerCastDoM:Mode$ SpellCast | ValidCard$ Card.IsRemembered | TriggerZones$ Command | Execute$ TrigRemoveSelf | Static$ True SVar:TriggerCastDoM:Mode$ SpellCast | ValidCard$ Card.IsRemembered | TriggerZones$ Command | Execute$ TrigRemoveSelf | Static$ True
SVar:TrigRemoveSelf:DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile SVar:TrigRemoveSelf:DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True

View File

@@ -4,8 +4,11 @@ Types:Creature Vampire Wizard
PT:3/3 PT:3/3
K:Flying K:Flying
T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player | CombatDamage$ True | Execute$ TrigChangeZone | TriggerZones$ Battlefield | TriggerDescription$ Whenever CARDNAME deals combat damage to a player, that player exiles a card from his or her hand. You may play that card for as long as it remains exiled. T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player | CombatDamage$ True | Execute$ TrigChangeZone | TriggerZones$ Battlefield | TriggerDescription$ Whenever CARDNAME deals combat damage to a player, that player exiles a card from his or her hand. You may play that card for as long as it remains exiled.
SVar:TrigChangeZone:AB$ ChangeZone | Cost$ 0 | Origin$ Hand | Destination$ Exile | DefinedPlayer$ TriggeredTarget | Chooser$ TriggeredTarget | ChangeType$ Card | ChangeNum$ 1 | RememberChanged$ True | SubAbility$ DBPump SVar:TrigChangeZone:DB$ ChangeZone | Origin$ Hand | Destination$ Exile | DefinedPlayer$ TriggeredTarget | Chooser$ TriggeredTarget | ChangeType$ Card | ChangeNum$ 1 | RememberChanged$ True | SubAbility$ DBEffect
SVar:DBPump:DB$ Pump | Defined$ Remembered | KW$ May be played by your opponent | PumpZone$ Exile | Permanent$ True | SubAbility$ DBCleanup SVar:DBEffect:DB$ Effect | StaticAbilities$ STPlay | Triggers$ TCleanup | RememberObjects$ Remembered | SVars$ DBExileSelf | SubAbility$ DBCleanup
SVar:STPlay:Mode$ Continuous | MayPlay$ True | EffectZone$ Command | Affected$ Card.IsRemembered | AffectedZone$ Exile | Description$ You may play a card for as long as it remains exiled.
SVar:TCleanup:Mode$ ChangesZone | ValidCard$ Card.IsRemembered | Origin$ Exile | Destination$ Any | Execute$ DBExileSelf | Static$ True
SVar:DBExileSelf:DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
A:AB$ Regenerate | Cost$ Sac<1/Human> | SpellDescription$ Regenerate CARDNAME. A:AB$ Regenerate | Cost$ Sac<1/Human> | SpellDescription$ Regenerate CARDNAME.
SVar:RemAIDeck:True SVar:RemAIDeck:True

View File

@@ -5,7 +5,8 @@ PT:4/4
A:AB$ Pump | Cost$ 1 | ValidTgts$ Creature | TgtZone$ Graveyard | TgtPrompt$ Select target creature card | PumpZone$ Graveyard | SubAbility$ PumpYour | RememberObjects$ Targeted | SpellDescription$ You may cast target creature card in a graveyard this turn. When you cast that card this turn, CARDNAME gains all activated abilities of that card until end of turn. A:AB$ Pump | Cost$ 1 | ValidTgts$ Creature | TgtZone$ Graveyard | TgtPrompt$ Select target creature card | PumpZone$ Graveyard | SubAbility$ PumpYour | RememberObjects$ Targeted | SpellDescription$ You may cast target creature card in a graveyard this turn. When you cast that card this turn, CARDNAME gains all activated abilities of that card until end of turn.
SVar:PumpYour:DB$ Pump | Defined$ Remembered | ConditionCheckSVar$ SelectKW | ConditionSVarCompare$ EQ1 | KW$ HIDDEN May be played | PumpZone$ Graveyard | SubAbility$ PumpTheir | References$ SelectKW SVar:PumpYour:DB$ Pump | Defined$ Remembered | ConditionCheckSVar$ SelectKW | ConditionSVarCompare$ EQ1 | KW$ HIDDEN May be played | PumpZone$ Graveyard | SubAbility$ PumpTheir | References$ SelectKW
SVar:PumpTheir:DB$ Pump | Defined$ Remembered | ConditionCheckSVar$ SelectKW | ConditionSVarCompare$ EQ0 | KW$ HIDDEN May be played by your opponent | PumpZone$ Graveyard | SubAbility$ FXCast | References$ SelectKW SVar:PumpTheir:DB$ Pump | Defined$ Remembered | ConditionCheckSVar$ SelectKW | ConditionSVarCompare$ EQ0 | KW$ HIDDEN May be played by your opponent | PumpZone$ Graveyard | SubAbility$ FXCast | References$ SelectKW
SVar:FXCast:DB$ Effect | Name$ Havengul Lich Delayed Trigger | Triggers$ DTCast | SVars$ StealAbs,STSteal,CleanupDT | RememberObjects$ Targeted SVar:FXCast:DB$ Effect | Name$ Havengul Lich Delayed Trigger | StaticAbilities$ STPlay | Triggers$ DTCast | SVars$ StealAbs,STSteal,CleanupDT | RememberObjects$ Targeted
SVar:STPlay:Mode$ Continuous | MayPlay$ True | EffectZone$ Command | Affected$ Card.IsRemembered | AffectedZone$ Graveyard | Description$ Until end of turn, you may cast a creature card in a graveyard.
SVar:DTCast:Mode$ SpellCast | ValidCard$ Card.IsRemembered | Execute$ StealAbs | TriggerDescription$ When you cast that card this turn, Havengul Lich gains all activated abilities of that card until end of turn. SVar:DTCast:Mode$ SpellCast | ValidCard$ Card.IsRemembered | Execute$ StealAbs | TriggerDescription$ When you cast that card this turn, Havengul Lich gains all activated abilities of that card until end of turn.
SVar:StealAbs:DB$ Effect | Name$ Havengul Lich effect | RememberObjects$ TriggeredCard | StaticAbilities$ STSteal | SubAbility$ CleanupDT SVar:StealAbs:DB$ Effect | Name$ Havengul Lich effect | RememberObjects$ TriggeredCard | StaticAbilities$ STSteal | SubAbility$ CleanupDT
SVar:STSteal:Mode$ Continuous | Affected$ EffectSource | EffectZone$ Command | GainsAbilitiesOf$ Creature.IsRemembered | GainsAbilitiesOfZones$ Library,Hand,Stack,Battlefield,Graveyard,Exile,Command SVar:STSteal:Mode$ Continuous | Affected$ EffectSource | EffectZone$ Command | GainsAbilitiesOf$ Creature.IsRemembered | GainsAbilitiesOfZones$ Library,Hand,Stack,Battlefield,Graveyard,Exile,Command

View File

@@ -5,9 +5,8 @@ PT:3/3
K:Morph:4 U U K:Morph:4 U U
T:Mode$ TurnFaceUp | ValidCard$ Card.Self | Execute$ TrigCounter | TriggerZones$ Battlefield | TriggerDescription$ When CARDNAME is turned face up, counter target spell. If that spell is countered this way, exile it instead of putting it into its owner's graveyard. You may cast that card without paying its mana cost as long as it remains exiled. T:Mode$ TurnFaceUp | ValidCard$ Card.Self | Execute$ TrigCounter | TriggerZones$ Battlefield | TriggerDescription$ When CARDNAME is turned face up, counter target spell. If that spell is countered this way, exile it instead of putting it into its owner's graveyard. You may cast that card without paying its mana cost as long as it remains exiled.
SVar:TrigCounter:AB$ Counter | Cost$ 0 | TargetType$ Spell | TgtPrompt$ Select target spell | ValidTgts$ Card | RememberCountered$ True | ForgetOtherTargets$ True | Destination$ Exile | SubAbility$ DBEffect SVar:TrigCounter:AB$ Counter | Cost$ 0 | TargetType$ Spell | TgtPrompt$ Select target spell | ValidTgts$ Card | RememberCountered$ True | ForgetOtherTargets$ True | Destination$ Exile | SubAbility$ DBEffect
SVar:DBEffect:DB$ Effect | Name$ Kheru Spellsnatcher Effect | RememberObjects$ Remembered | StaticAbilities$ PlayOpp,PlayYou | Duration$ Permanent | Triggers$ TrigCleanup | SVars$ DBCleanup | References$ PlayOpp,PlayYou,TrigCleanup,DBCleanup SVar:DBEffect:DB$ Effect | RememberObjects$ Remembered | StaticAbilities$ STPlay | Duration$ Permanent | Triggers$ TrigCleanup | SVars$ DBCleanup | References$ PlayOpp,PlayYou,TrigCleanup,DBCleanup
SVar:PlayOpp:Mode$ Continuous | EffectZone$ Command | Affected$ Card.IsRemembered+OppOwn | AffectedZone$ Exile | AddHiddenKeyword$ May be played by your opponent without paying its mana cost | Description$ You may play cards exiled with Kheru Spellsnatcher. SVar:STPlay:Mode$ Continuous | MayPlay$ True | MayPlayWithoutManaCost$ True | EffectZone$ Command | Affected$ Card.IsRemembered | AffectedZone$ Exile | Description$ You may cast cards without paying their mana cost as long as they remain exiled.
SVar:PlayYou:Mode$ Continuous | EffectZone$ Command | Affected$ Card.IsRemembered+YouOwn | AffectedZone$ Exile | AddHiddenKeyword$ May be played without paying its mana cost
SVar:TrigCleanup:Mode$ ChangesZone | ValidCard$ Card.IsRemembered | Origin$ Exile | Destination$ Any | TriggerZones$ Command | Execute$ DBCleanup | Static$ True SVar:TrigCleanup:Mode$ ChangesZone | ValidCard$ Card.IsRemembered | Origin$ Exile | Destination$ Any | TriggerZones$ Command | Execute$ DBCleanup | Static$ True
SVar:DBCleanup:DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile SVar:DBCleanup:DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile
SVar:RemAIDeck:True SVar:RemAIDeck:True

View File

@@ -3,9 +3,12 @@ ManaCost:3 U
Types:Creature Faerie Rogue Types:Creature Faerie Rogue
PT:1/4 PT:1/4
K:Flying K:Flying
A:AB$ Mill | Cost$ 1 U Q | ValidTgts$ Opponent | TgtPrompt$ Select target opponent | NumCards$ 1 | Destination$ Exile | RememberMilled$ True | SubAbility$ DBPump | SpellDescription$ Target opponent exiles the top card of his or her library. Until end of turn, you may play that card. A:AB$ Mill | Cost$ 1 U Q | ValidTgts$ Opponent | TgtPrompt$ Select target opponent | NumCards$ 1 | Destination$ Exile | RememberMilled$ True | SubAbility$ DBEffect | SpellDescription$ Target opponent exiles the top card of his or her library. Until end of turn, you may play that card.
SVar:DBPump:DB$ Pump | Defined$ Remembered | KW$ May be played by your opponent | PumpZone$ Exile | SubAbility$ DBCleanup SVar:DBEffect:DB$ Effect | Duration$ EndOfTurn | RememberObjects$ Remembered | StaticAbilities$ STPlay | Triggers$ TrigCleanup | SVars$ DBExileSelf | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:STPlay:Mode$ Continuous | MayPlay$ True | EffectZone$ Command | Affected$ Card.IsRemembered | AffectedZone$ Exile | Description$ You may play a card this turn.
SVar:TrigCleanup:Mode$ ChangesZone | ValidCard$ Card.IsRemembered | Origin$ Exile | Destination$ Any | TriggerZones$ Command | Execute$ DBExileSelf | Static$ True
SVar:DBExileSelf:DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ Truek
SVar:RemAIDeck:True SVar:RemAIDeck:True
SVar:Picture:http://www.wizards.com/global/images/magic/general/knacksaw_clique.jpg SVar:Picture:http://www.wizards.com/global/images/magic/general/knacksaw_clique.jpg
Oracle:Flying\n{1}{U}, {Q}: Target opponent exiles the top card of his or her library. Until end of turn, you may play that card. ({Q} is the untap symbol.) Oracle:Flying\n{1}{U}, {Q}: Target opponent exiles the top card of his or her library. Until end of turn, you may play that card. ({Q} is the untap symbol.)

View File

@@ -2,9 +2,11 @@ Name:Muse Vessel
ManaCost:4 ManaCost:4
Types:Artifact Types:Artifact
A:AB$ ChangeZone | Cost$ 3 T | ValidTgts$ Player | TgtPrompt$ Select target player | SorcerySpeed$ True | Origin$ Hand | Destination$ Exile | ChangeType$ Card | ChangeNum$ 1 | RememberChanged$ True | Chooser$ Targeted | Hidden$ True | IsCurse$ True | Mandatory$ True | SpellDescription$ Target player exiles a card from his or her hand. Activate this ability only any time you could cast a sorcery. A:AB$ ChangeZone | Cost$ 3 T | ValidTgts$ Player | TgtPrompt$ Select target player | SorcerySpeed$ True | Origin$ Hand | Destination$ Exile | ChangeType$ Card | ChangeNum$ 1 | RememberChanged$ True | Chooser$ Targeted | Hidden$ True | IsCurse$ True | Mandatory$ True | SpellDescription$ Target player exiles a card from his or her hand. Activate this ability only any time you could cast a sorcery.
A:AB$ ChooseCard | Cost$ 1 | ChoiceZone$ Exile | Choices$ Card.IsRemembered | Amount$ 1 | ChoiceTitle$ Choose a card exiled with Muse Vessel | SubAbility$ DBPlayYouOwn | AILogic$ Never | SpellDescription$ Choose a card exiled with CARDNAME. You may play that card this turn. A:AB$ ChooseCard | Cost$ 1 | ChoiceZone$ Exile | Choices$ Card.IsRemembered | Amount$ 1 | ChoiceTitle$ Choose a card exiled with Muse Vessel | SubAbility$ DBEffect | AILogic$ Never | SpellDescription$ Choose a card exiled with CARDNAME. You may play that card this turn.
SVar:DBPlayYouOwn:DB$ Pump | Defined$ ChosenCard | KW$ HIDDEN May be played | PumpZone$ Exile | ConditionDefined$ ChosenCard | ConditionPresent$ Card.YouOwn | ConditionCompare$ EQ1 | SubAbility$ DBPlayOppOwn SVar:DBEffect:DB$ Effect | Duration$ EndOfTurn | StaticAbilities$ STPlay | Triggers$ TrigCleanup | SVars$ DBExileSelf | RememberObjects$ ChosenCard | SubAbility$ DBCleanup
SVar:DBPlayOppOwn:DB$ Pump | Defined$ ChosenCard | KW$ HIDDEN May be played by your opponent | PumpZone$ Exile | ConditionDefined$ ChosenCard | ConditionPresent$ Card.OppOwn | ConditionCompare$ EQ1 SVar:STPlay:Mode$ Continuous | MayPlay$ True | EffectZone$ Command | Affected$ Card.IsRemembered | AffectedZone$ Exile | Description$ You may play a card this turn.
SVar:TrigCleanup:Mode$ ChangesZone | ValidCard$ Card.IsRemembered | Origin$ Exile | Destination$ Any | TriggerZones$ Command | Execute$ DBExileSelf | Static$ True
SVar:DBExileSelf:DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile
T:Mode$ ChangesZone | Origin$ Exile | Destination$ Any | Static$ True | ValidCard$ Card.IsRemembered | Execute$ DBForget T:Mode$ ChangesZone | Origin$ Exile | Destination$ Any | Static$ True | ValidCard$ Card.IsRemembered | Execute$ DBForget
SVar:DBForget:DB$ Pump | Defined$ TriggeredCard | ForgetObjects$ TriggeredCard SVar:DBForget:DB$ Pump | Defined$ TriggeredCard | ForgetObjects$ TriggeredCard
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | Static$ True | ValidCard$ Card.Self | Execute$ DBCleanup T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | Static$ True | ValidCard$ Card.Self | Execute$ DBCleanup

View File

@@ -5,8 +5,7 @@ PT:2/3
K:Flying K:Flying
T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player | CombatDamage$ True | Execute$ TrigMill | TriggerZones$ Battlefield | TriggerDescription$ Whenever CARDNAME deals combat damage to a player, that player exiles the top card of his or her library. T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player | CombatDamage$ True | Execute$ TrigMill | TriggerZones$ Battlefield | TriggerDescription$ Whenever CARDNAME deals combat damage to a player, that player exiles the top card of his or her library.
SVar:TrigMill:AB$ Mill | Cost$ 0 | Defined$ TriggeredTarget | NumCards$ 1 | Destination$ Exile | RememberMilled$ True SVar:TrigMill:AB$ Mill | Cost$ 0 | Defined$ TriggeredTarget | NumCards$ 1 | Destination$ Exile | RememberMilled$ True
S:Mode$ Continuous | Affected$ Card.IsRemembered+OppOwn | AffectedZone$ Exile | AddHiddenKeyword$ May be played by your opponent | Description$ You may play cards exiled with CARDNAME. S:Mode$ Continuous | MayPlay$ True | Affected$ Card.IsRemembered+OppOwn | AffectedZone$ Exile | Description$ You may play cards exiled with CARDNAME.
S:Mode$ Continuous | Affected$ Card.IsRemembered+YouOwn | AffectedZone$ Exile | AddHiddenKeyword$ May be played
T:Mode$ ChangesZone | Origin$ Exile | Destination$ Any | Static$ True | ValidCard$ Card.IsRemembered | Execute$ DBForget T:Mode$ ChangesZone | Origin$ Exile | Destination$ Any | Static$ True | ValidCard$ Card.IsRemembered | Execute$ DBForget
SVar:DBForget:DB$ Pump | Defined$ TriggeredCard | ForgetObjects$ TriggeredCard SVar:DBForget:DB$ Pump | Defined$ TriggeredCard | ForgetObjects$ TriggeredCard
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | Static$ True | ValidCard$ Card.Self | Execute$ DBCleanup T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | Static$ True | ValidCard$ Card.Self | Execute$ DBCleanup

View File

@@ -1,8 +1,11 @@
Name:Ornate Kanzashi Name:Ornate Kanzashi
ManaCost:5 ManaCost:5
Types:Artifact Types:Artifact
A:AB$ Mill | Cost$ 2 T | ValidTgts$ Opponent | TgtPrompt$ Select target opponent | NumCards$ 1 | Destination$ Exile | RememberMilled$ True | SubAbility$ DBPump | SpellDescription$ Target opponent exiles the top card of his or her library. You may play that card this turn. A:AB$ Mill | Cost$ 2 T | ValidTgts$ Opponent | TgtPrompt$ Select target opponent | NumCards$ 1 | Destination$ Exile | RememberMilled$ True | SubAbility$ DBEffect | SpellDescription$ Target opponent exiles the top card of his or her library. You may play that card this turn.
SVar:DBPump:DB$ Pump | Defined$ Remembered | KW$ May be played by your opponent | PumpZone$ Exile | SubAbility$ DBCleanup SVar:DBEffect:DB$ Effect | Duration$ EndOfTurn | RememberObjects$ Remembered | StaticAbilities$ STPlay | Triggers$ TrigCleanup | SVars$ DBExileSelf | SubAbility$ DBCleanup
SVar:STPlay:Mode$ Continuous | MayPlay$ True | EffectZone$ Command | Affected$ Card.IsRemembered | AffectedZone$ Exile | Description$ You may play a card this turn.
SVar:TrigCleanup:Mode$ ChangesZone | ValidCard$ Card.IsRemembered | Origin$ Exile | Destination$ Any | TriggerZones$ Command | Execute$ DBExileSelf | Static$ True
SVar:DBExileSelf:DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:RemAIDeck:True SVar:RemAIDeck:True
SVar:Picture:http://www.wizards.com/global/images/magic/general/ornate_kanzashi.jpg SVar:Picture:http://www.wizards.com/global/images/magic/general/ornate_kanzashi.jpg

View File

@@ -6,9 +6,9 @@ SVar:DBChangeZone:DB$ ChangeZoneAll | ChangeType$ Instant.IsRemembered,Sorcery.I
SVar:DBForgetOther:DB$ Cleanup | ClearRemembered$ True | ConditionCheckSVar$ X | ConditionSVarCompare$ EQ0 | SubAbility$ DBEffect SVar:DBForgetOther:DB$ Cleanup | ClearRemembered$ True | ConditionCheckSVar$ X | ConditionSVarCompare$ EQ0 | SubAbility$ DBEffect
SVar:DBEffect:DB$ Effect | StaticAbilities$ MischiefPlay | Triggers$ TrigEOT,TrigChangesZone | SVars$ MischiefCleanup,MischiefReturn | RememberObjects$ Remembered | Permanent$ True | ConditionCheckSVar$ X | ConditionSVarCompare$ GE1 | SubAbility$ DBCleanup SVar:DBEffect:DB$ Effect | StaticAbilities$ MischiefPlay | Triggers$ TrigEOT,TrigChangesZone | SVars$ MischiefCleanup,MischiefReturn | RememberObjects$ Remembered | Permanent$ True | ConditionCheckSVar$ X | ConditionSVarCompare$ GE1 | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:MischiefPlay:Mode$ Continuous | EffectZone$ Command | Affected$ Card.IsRemembered+OppOwn | AffectedZone$ Exile | AddHiddenKeyword$ May be played by your opponent without paying its mana cost | Description$ You may play that exiled card. SVar:MischiefPlay:Mode$ Continuous | MayPlay$ True | MayPlayWithoutManaCost$ True | EffectZone$ Command | Affected$ Card.IsRemembered+OppOwn | AffectedZone$ Exile | Description$ You may cast a card without paying its mana cost as long as it remains exiled.
SVar:TrigEOT:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | TriggerZones$ Command | Execute$ MischiefReturn | TriggerDescription$ At the beginning of the next end step, if you haven't cast it, return it to its owner's hand. SVar:TrigEOT:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | TriggerZones$ Command | Execute$ MischiefReturn | TriggerDescription$ At the beginning of the next end step, if you haven't cast it, return it to its owner's hand.
SVar:MischiefReturn:DB$ ChangeZone | Defined$ Remembered | Origin$ Exile | Destination$ Hand | SVar:MischiefReturn:DB$ ChangeZone | Defined$ Remembered | Origin$ Exile | Destination$ Hand
SVar:TrigChangesZone:Mode$ ChangesZone | Origin$ Exile | Destination$ Any | Static$ True | ValidCard$ Card.IsRemembered | Execute$ MischiefCleanup SVar:TrigChangesZone:Mode$ ChangesZone | Origin$ Exile | Destination$ Any | Static$ True | ValidCard$ Card.IsRemembered | Execute$ MischiefCleanup
SVar:MischiefCleanup:DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile SVar:MischiefCleanup:DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile
SVar:X:Count$ValidExile Instant.IsRemembered,Sorcery.IsRemembered SVar:X:Count$ValidExile Instant.IsRemembered,Sorcery.IsRemembered

View File

@@ -1,8 +1,12 @@
Name:Praetor's Grasp Name:Praetor's Grasp
ManaCost:1 B B ManaCost:1 B B
Types:Sorcery Types:Sorcery
A:SP$ ChangeZone | Cost$ 1 B B | Origin$ Library | Destination$ Exile | ValidTgts$ Opponent | ChangeType$ Card | ChangeNum$ 1 | IsCurse$ True | RememberChanged$ True | SubAbility$ DBPump | StackDescription$ SpellDescription | SpellDescription$ Search target opponent's library for a card and exile it face down. Then that player shuffles his or her library. You may look at and play that card for as long as it remains exiled. A:SP$ ChangeZone | Cost$ 1 B B | Origin$ Library | Destination$ Exile | ExileFaceDown$ True | ValidTgts$ Opponent | ChangeType$ Card | ChangeNum$ 1 | IsCurse$ True | RememberChanged$ True | SubAbility$ DBEffect | StackDescription$ SpellDescription | SpellDescription$ Search target opponent's library for a card and exile it face down. Then that player shuffles his or her library. You may look at and play that card for as long as it remains exiled.
SVar:DBPump:DB$ Pump | Defined$ Remembered | KW$ Your opponent may look at this card. & May be played by your opponent | PumpZone$ Exile | Permanent$ True SVar:DBEffect:DB$ Effect | MayLookAt$ True | MayPlay$ True | RememberObjects$ Remembered | StaticAbilities$ STPlay | Duration$ Permanent | Triggers$ TrigCleanup | SVars$ DBExileSelf | SubAbility$ DBCleanup
SVar:STPlay:Mode$ Continuous | MayLookAt$ True | MayPlay$ True | EffectZone$ Command | Affected$ Card.IsRemembered | AffectedZone$ Exile | Description$ You may look at and play a card as long as it remains exiled.
SVar:TrigCleanup:Mode$ ChangesZone | ValidCard$ Card.IsRemembered | Origin$ Exile | Destination$ Any | TriggerZones$ Command | Execute$ DBExileSelf | Static$ True
SVar:DBExileSelf:DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:RemAIDeck:True SVar:RemAIDeck:True
SVar:Picture:http://www.wizards.com/global/images/magic/general/praetors_grasp.jpg SVar:Picture:http://www.wizards.com/global/images/magic/general/praetors_grasp.jpg
Oracle:Search target opponent's library for a card and exile it face down. Then that player shuffles his or her library. You may look at and play that card for as long as it remains exiled. Oracle:Search target opponent's library for a card and exile it face down. Then that player shuffles his or her library. You may look at and play that card for as long as it remains exiled.

View File

@@ -0,0 +1,18 @@
Name:Shared Fate
ManaCost:4 U
Types:Enchantment
Text:If a player would draw a card, that player exiles the top card of an opponent's library face down instead. Each player may look at and play cards he or she exiled with CARDNAME.
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigEffects | Static$ True
#Create an effect for each player. The effect contains both Shared Fate's abilities.
SVar:TrigEffects:DB$ RepeatEach | RepeatPlayers$ Each | RepeatSubAbility$ DBEffect
SVar:DBEffect:DB$ Effect | EffectOwner$ Remembered | StaticAbilities$ STPlay | Triggers$ TrigCleanup | ReplacementEffects$ RDraw | SVars$ DBChooseOpp,DBExile,DBCleanup | Duration$ UntilHostLeavesPlay
SVar:RDraw:Event$ Draw | ActiveZones$ Command | ValidPlayer$ You | ReplaceWith$ DBChooseOpp | Description$ If you would draw a card, exile the top card of an opponent's library face down instead.
SVar:DBChooseOpp:DB$ ChoosePlayer | ChoiceTitle$ Choose an opponent whose top library card to exile | Choices$ Player.Opponent | AILogic$ Curse | SubAbility$ DBExile
SVar:DBExile:DB$ Mill | NumCards$ 1 | Destination$ Exile | ExileFaceDown$ True | Defined$ Player.Chosen | RememberMilled$ True
SVar:STPlay:Mode$ Continuous | MayLookAt$ True | MayPlay$ True | EffectZone$ Command | Affected$ Card.IsRemembered | AffectedZone$ Exile | Description$ You may look at and play cards exiled with Shared Fate.
SVar:TrigCleanup:Mode$ ChangesZone | ValidCard$ Card.IsRemembered | Origin$ Exile | Destination$ Any | TriggerZones$ Command | Execute$ DBCleanup | Static$ True
SVar:DBCleanup:DB$ Cleanup | ForgetDefined$ TriggeredCard
SVar:RemAIDeck:True
SVar:RemRandomDeck:True
SVar:Picture:http://www.wizards.com/global/images/magic/general/shared_fate.jpg
Oracle:If a player would draw a card, that player exiles the top card of an opponent's library face down instead.\nEach player may look at and play cards he or she exiled with Shared Fate.

View File

@@ -17,14 +17,20 @@
*/ */
package forge.player; package forge.player;
import java.util.Collections;
import org.apache.commons.lang3.StringUtils;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import forge.card.CardStateName;
import forge.card.CardType; import forge.card.CardType;
import forge.card.MagicColor; import forge.card.MagicColor;
import forge.game.Game; import forge.game.Game;
import forge.game.GameObject; import forge.game.GameObject;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardPlayOption;
import forge.game.cost.CostPartMana; import forge.game.cost.CostPartMana;
import forge.game.cost.CostPayment; import forge.game.cost.CostPayment;
import forge.game.mana.ManaPool; import forge.game.mana.ManaPool;
@@ -37,10 +43,6 @@ import forge.game.spellability.TargetRestrictions;
import forge.game.zone.Zone; import forge.game.zone.Zone;
import forge.util.FCollection; import forge.util.FCollection;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
/** /**
* <p> * <p>
* SpellAbility_Requirements class. * SpellAbility_Requirements class.
@@ -66,18 +68,27 @@ public class HumanPlaySpellAbility {
// used to rollback // used to rollback
Zone fromZone = null; Zone fromZone = null;
CardStateName fromState = null;
int zonePosition = 0; int zonePosition = 0;
final ManaPool manapool = human.getManaPool(); final ManaPool manapool = human.getManaPool();
final Card c = ability.getHostCard(); final Card c = ability.getHostCard();
boolean manaConversion = (ability.isSpell() && c.hasKeyword("May spend mana as though it were mana of any color to cast CARDNAME")); final CardPlayOption option = c.mayPlay(human);
boolean manaConversion = (ability.isSpell() && (c.hasKeyword("May spend mana as though it were mana of any color to cast CARDNAME")
|| (option != null && option.isIgnoreManaCostColor())));
boolean playerManaConversion = human.hasManaConversion() boolean playerManaConversion = human.hasManaConversion()
&& human.getController().confirmAction(ability, null, "Do you want to spend mana as though it were mana of any color to pay the cost?"); && human.getController().confirmAction(ability, null, "Do you want to spend mana as though it were mana of any color to pay the cost?");
if (ability instanceof Spell && !c.isCopiedSpell()) { if (ability instanceof Spell && !c.isCopiedSpell()) {
fromZone = game.getZoneOf(c); fromZone = game.getZoneOf(c);
fromState = c.getCurrentStateName();
if (fromZone != null) { if (fromZone != null) {
zonePosition = fromZone.getCards().indexOf(c); zonePosition = fromZone.getCards().indexOf(c);
} }
// Turn face-down card face up (except case of morph spell)
if (ability instanceof Spell && !((Spell) ability).isCastFaceDown() && fromState == CardStateName.FaceDown) {
c.turnFaceUp();
}
ability.setHostCard(game.getAction().moveToStack(c)); ability.setHostCard(game.getAction().moveToStack(c));
} }
@@ -100,7 +111,7 @@ public class HumanPlaySpellAbility {
if (!prerequisitesMet) { if (!prerequisitesMet) {
if (!ability.isTrigger()) { if (!ability.isTrigger()) {
rollbackAbility(fromZone, zonePosition); rollbackAbility(fromZone, fromState, zonePosition);
if (ability.getHostCard().isMadness()) { if (ability.getHostCard().isMadness()) {
// if a player failed to play madness cost, move the card to graveyard // if a player failed to play madness cost, move the card to graveyard
game.getAction().moveToGraveyard(c); game.getAction().moveToGraveyard(c);
@@ -125,8 +136,7 @@ public class HumanPlaySpellAbility {
if (skipStack) { if (skipStack) {
AbilityUtils.resolve(ability); AbilityUtils.resolve(ability);
} } else {
else {
enusureAbilityHasDescription(ability); enusureAbilityHasDescription(ability);
game.getStack().addAndUnfreeze(ability); game.getStack().addAndUnfreeze(ability);
} }
@@ -182,13 +192,14 @@ public class HumanPlaySpellAbility {
} }
} }
private void rollbackAbility(Zone fromZone, int zonePosition) { private void rollbackAbility(final Zone fromZone, final CardStateName fromState, final int zonePosition) {
// cancel ability during target choosing // cancel ability during target choosing
final Game game = ability.getActivatingPlayer().getGame(); final Game game = ability.getActivatingPlayer().getGame();
if (fromZone != null) { // and not a copy if (fromZone != null) { // and not a copy
// add back to where it came from // add back to where it came from
game.getAction().moveTo(fromZone, ability.getHostCard(), zonePosition >= 0 ? Integer.valueOf(zonePosition) : null); game.getAction().moveTo(fromZone, ability.getHostCard(), zonePosition >= 0 ? Integer.valueOf(zonePosition) : null);
ability.getHostCard().setState(fromState, true);
} }
clearTargets(ability); clearTargets(ability);
@@ -255,7 +266,7 @@ public class HumanPlaySpellAbility {
for (String aVar : announce.split(",")) { for (String aVar : announce.split(",")) {
String varName = aVar.trim(); String varName = aVar.trim();
if ("CreatureType".equals(varName)) { if ("CreatureType".equals(varName)) {
String choice = pc.chooseSomeType("Creature", ability, CardType.Constant.CREATURE_TYPES, new ArrayList<String>()); final String choice = pc.chooseSomeType("Creature", ability, CardType.Constant.CREATURE_TYPES, Collections.<String>emptyList());
ability.getHostCard().setChosenType(choice); ability.getHostCard().setChosenType(choice);
} }
if ("ChooseNumber".equals(varName)) { if ("ChooseNumber".equals(varName)) {