Merge branch 'playTriggerChangesZoneAll' into 'master'

Update PhaseHandler for Trigger ChangesZoneAll

See merge request core-developers/forge!1214
This commit is contained in:
Michael Kamensky
2018-12-25 19:00:28 +00:00
33 changed files with 386 additions and 335 deletions

View File

@@ -1514,7 +1514,7 @@ public class ComputerUtilMana {
final Card offering = sa.getSacrificedAsOffering(); final Card offering = sa.getSacrificedAsOffering();
offering.setUsedToPay(false); offering.setUsedToPay(false);
if (costIsPaid && !test) { if (costIsPaid && !test) {
sa.getHostCard().getController().getGame().getAction().sacrifice(offering, sa); sa.getHostCard().getGame().getAction().sacrifice(offering, sa, null);
} }
sa.resetSacrificedAsOffering(); sa.resetSacrificedAsOffering();
} }
@@ -1522,7 +1522,7 @@ public class ComputerUtilMana {
final Card emerge = sa.getSacrificedAsEmerge(); final Card emerge = sa.getSacrificedAsEmerge();
emerge.setUsedToPay(false); emerge.setUsedToPay(false);
if (costIsPaid && !test) { if (costIsPaid && !test) {
sa.getHostCard().getController().getGame().getAction().sacrifice(emerge, sa); sa.getHostCard().getGame().getAction().sacrifice(emerge, sa, null);
} }
sa.resetSacrificedAsEmerge(); sa.resetSacrificedAsEmerge();
} }

View File

@@ -126,7 +126,8 @@ public class MustBlockAi extends SpellAbilityAi {
return chance; return chance;
} }
private List<Card> determineGoodBlockers(final Card attacker,final Player ai,final Player defender, SpellAbility sa, final boolean onlyLethal, final boolean testTapped) { private List<Card> determineGoodBlockers(final Card attacker, final Player ai, Player defender, SpellAbility sa,
final boolean onlyLethal, final boolean testTapped) {
final Card source = sa.getHostCard(); final Card source = sa.getHostCard();
final TargetRestrictions abTgt = sa.getTargetRestrictions(); final TargetRestrictions abTgt = sa.getTargetRestrictions();

View File

@@ -926,6 +926,8 @@ public class GameAction {
checkStaticAbilities(false, affectedCards, CardCollection.EMPTY); checkStaticAbilities(false, affectedCards, CardCollection.EMPTY);
boolean checkAgain = false; boolean checkAgain = false;
CardZoneTable table = new CardZoneTable();
for (final Player p : game.getPlayers()) { for (final Player p : game.getPlayers()) {
for (final ZoneType zt : ZoneType.values()) { for (final ZoneType zt : ZoneType.values()) {
if (zt == ZoneType.Battlefield) { if (zt == ZoneType.Battlefield) {
@@ -973,8 +975,8 @@ public class GameAction {
} }
} }
checkAgain |= stateBasedAction_Saga(c); checkAgain |= stateBasedAction_Saga(c, table);
checkAgain |= stateBasedAction704_attach(c); // Attachment checkAgain |= stateBasedAction704_attach(c, table); // Attachment
if (c.isCreature() && c.isAttachedToEntity()) { // Rule 704.5q - Creature attached to an object or player, becomes unattached if (c.isCreature() && c.isAttachedToEntity()) { // Rule 704.5q - Creature attached to an object or player, becomes unattached
c.unattachFromEntity(c.getEntityAttachedTo()); c.unattachFromEntity(c.getEntityAttachedTo());
@@ -999,24 +1001,19 @@ public class GameAction {
orderedNoRegCreats = true; orderedNoRegCreats = true;
} }
for (Card c : noRegCreats) { for (Card c : noRegCreats) {
sacrificeDestroy(c, null); sacrificeDestroy(c, null, table);
} }
} }
if (desCreats != null) { if (desCreats != null) {
if (desCreats.size() > 1 && !orderedDesCreats) { if (desCreats.size() > 1 && !orderedDesCreats) {
desCreats = CardLists.filter(desCreats, new Predicate<Card>() { desCreats = CardLists.filter(desCreats, CardPredicates.Presets.CAN_BE_DESTROYED);
@Override
public boolean apply(Card card) {
return card.canBeDestroyed();
}
});
if (!desCreats.isEmpty()) { if (!desCreats.isEmpty()) {
desCreats = (CardCollection) GameActionUtil.orderCardsByTheirOwners(game, desCreats, ZoneType.Graveyard); desCreats = (CardCollection) GameActionUtil.orderCardsByTheirOwners(game, desCreats, ZoneType.Graveyard);
} }
orderedDesCreats = true; orderedDesCreats = true;
} }
for (Card c : desCreats) { for (Card c : desCreats) {
destroy(c, null); destroy(c, null, true, table);
} }
} }
setHoldCheckingStaticAbilities(false); setHoldCheckingStaticAbilities(false);
@@ -1026,20 +1023,21 @@ public class GameAction {
} }
for (Player p : game.getPlayers()) { for (Player p : game.getPlayers()) {
if (handleLegendRule(p)) { if (handleLegendRule(p, table)) {
checkAgain = true; checkAgain = true;
} }
if (handlePlaneswalkerRule(p)) { if (handlePlaneswalkerRule(p, table)) {
checkAgain = true; checkAgain = true;
} }
} }
// 704.5m World rule // 704.5m World rule
checkAgain |= handleWorldRule(); checkAgain |= handleWorldRule(table);
if (game.getCombat() != null) { if (game.getCombat() != null) {
game.getCombat().removeAbsentCombatants(); game.getCombat().removeAbsentCombatants();
} }
table.triggerChangesZoneAll(game);
if (!checkAgain) { if (!checkAgain) {
break; // do not continue the loop break; // do not continue the loop
} }
@@ -1072,7 +1070,7 @@ public class GameAction {
} }
} }
private boolean stateBasedAction_Saga(Card c) { private boolean stateBasedAction_Saga(Card c, CardZoneTable table) {
boolean checkAgain = false; boolean checkAgain = false;
if (!c.getType().hasSubtype("Saga")) { if (!c.getType().hasSubtype("Saga")) {
return false; return false;
@@ -1085,13 +1083,13 @@ public class GameAction {
} }
if (!game.getStack().hasSimultaneousStackEntries() && if (!game.getStack().hasSimultaneousStackEntries() &&
!game.getStack().hasSourceOnStack(c, SpellAbilityPredicates.isChapter())) { !game.getStack().hasSourceOnStack(c, SpellAbilityPredicates.isChapter())) {
sacrifice(c, null); sacrifice(c, null, table);
checkAgain = true; checkAgain = true;
} }
return checkAgain; return checkAgain;
} }
private boolean stateBasedAction704_attach(Card c) { private boolean stateBasedAction704_attach(Card c, CardZoneTable table) {
boolean checkAgain = false; boolean checkAgain = false;
if (c.isAttachedToEntity()) { if (c.isAttachedToEntity()) {
@@ -1113,7 +1111,7 @@ public class GameAction {
// cleanup aura // cleanup aura
if (c.isAura() && c.isInPlay() && !c.isEnchanting()) { if (c.isAura() && c.isInPlay() && !c.isEnchanting()) {
moveToGraveyard(c, null, null); sacrificeDestroy(c, null, table);
checkAgain = true; checkAgain = true;
} }
return checkAgain; return checkAgain;
@@ -1243,7 +1241,7 @@ public class GameAction {
game.getStack().clearSimultaneousStack(); game.getStack().clearSimultaneousStack();
} }
private boolean handlePlaneswalkerRule(Player p) { private boolean handlePlaneswalkerRule(Player p, CardZoneTable table) {
// get all Planeswalkers // get all Planeswalkers
final List<Card> list = CardLists.filter(p.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANESWALKERS); final List<Card> list = CardLists.filter(p.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANESWALKERS);
boolean recheck = false; boolean recheck = false;
@@ -1252,7 +1250,7 @@ public class GameAction {
for (Card c : list) { for (Card c : list) {
if (c.getCounters(CounterType.LOYALTY) <= 0) { if (c.getCounters(CounterType.LOYALTY) <= 0) {
moveToGraveyard(c, null, null); sacrificeDestroy(c, null, table);
// Play the Destroy sound // Play the Destroy sound
game.fireEvent(new GameEventCardDestroyed()); game.fireEvent(new GameEventCardDestroyed());
recheck = true; recheck = true;
@@ -1286,7 +1284,7 @@ public class GameAction {
return recheck; return recheck;
} }
private boolean handleLegendRule(Player p) { private boolean handleLegendRule(Player p, CardZoneTable table) {
final List<Card> a = CardLists.getType(p.getCardsIn(ZoneType.Battlefield), "Legendary"); final List<Card> a = CardLists.getType(p.getCardsIn(ZoneType.Battlefield), "Legendary");
if (a.isEmpty() || game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule)) { if (a.isEmpty() || game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule)) {
return false; return false;
@@ -1314,7 +1312,7 @@ public class GameAction {
Card toKeep = p.getController().chooseSingleEntityForEffect(new CardCollection(cc), new AbilitySub(ApiType.InternalLegendaryRule, null, null, null), "You have multiple legendary permanents named \""+name+"\" in play.\n\nChoose the one to stay on battlefield (the rest will be moved to graveyard)"); Card toKeep = p.getController().chooseSingleEntityForEffect(new CardCollection(cc), new AbilitySub(ApiType.InternalLegendaryRule, null, null, null), "You have multiple legendary permanents named \""+name+"\" in play.\n\nChoose the one to stay on battlefield (the rest will be moved to graveyard)");
for (Card c: cc) { for (Card c: cc) {
if (c != toKeep) { if (c != toKeep) {
sacrificeDestroy(c, null); sacrificeDestroy(c, null, table);
} }
} }
game.fireEvent(new GameEventCardDestroyed()); game.fireEvent(new GameEventCardDestroyed());
@@ -1323,7 +1321,7 @@ public class GameAction {
return recheck; return recheck;
} }
private boolean handleWorldRule() { private boolean handleWorldRule(CardZoneTable table) {
final List<Card> worlds = CardLists.getType(game.getCardsIn(ZoneType.Battlefield), "World"); final List<Card> worlds = CardLists.getType(game.getCardsIn(ZoneType.Battlefield), "World");
if (worlds.size() <= 1) { if (worlds.size() <= 1) {
return false; return false;
@@ -1348,35 +1346,28 @@ public class GameAction {
} }
for (Card c : worlds) { for (Card c : worlds) {
sacrificeDestroy(c, null); sacrificeDestroy(c, null, table);
game.fireEvent(new GameEventCardDestroyed()); game.fireEvent(new GameEventCardDestroyed());
} }
return true; return true;
} }
@Deprecated
public final Card sacrifice(final Card c, final SpellAbility source) { public final Card sacrifice(final Card c, final SpellAbility source) {
return sacrifice(c, source, null);
}
public final Card sacrifice(final Card c, final SpellAbility source, CardZoneTable table) {
if (!c.canBeSacrificedBy(source)) { if (!c.canBeSacrificedBy(source)) {
return null; return null;
} }
c.getController().addSacrificedThisTurn(c, source); c.getController().addSacrificedThisTurn(c, source);
return sacrificeDestroy(c, source); return sacrificeDestroy(c, source, table);
} }
public final boolean destroy(final Card c, final SpellAbility sa) { public final boolean destroy(final Card c, final SpellAbility sa, final boolean regenerate, CardZoneTable table) {
if (!c.canBeDestroyed()) {
return false;
}
return destroy(c, sa, true);
}
public final boolean destroyNoRegeneration(final Card c, final SpellAbility sa) {
return destroy(c, sa, false);
}
public final boolean destroy(final Card c, final SpellAbility sa, final boolean regenerate) {
Player activator = null; Player activator = null;
if (!c.canBeDestroyed()) { if (!c.canBeDestroyed()) {
return false; return false;
@@ -1408,7 +1399,7 @@ public class GameAction {
runParams.put("Causer", activator); runParams.put("Causer", activator);
game.getTriggerHandler().runTrigger(TriggerType.Destroyed, runParams, false); game.getTriggerHandler().runTrigger(TriggerType.Destroyed, runParams, false);
final Card sacrificed = sacrificeDestroy(c, sa); final Card sacrificed = sacrificeDestroy(c, sa, table);
return sacrificed != null; return sacrificed != null;
} }
@@ -1416,12 +1407,15 @@ public class GameAction {
* @return the sacrificed Card in its new location, or {@code null} if the * @return the sacrificed Card in its new location, or {@code null} if the
* sacrifice wasn't successful. * sacrifice wasn't successful.
*/ */
public final Card sacrificeDestroy(final Card c, SpellAbility cause) { protected final Card sacrificeDestroy(final Card c, SpellAbility cause, CardZoneTable table) {
if (!c.isInPlay()) { if (!c.isInPlay()) {
return null; return null;
} }
final Card newCard = moveToGraveyard(c, cause, null); final Card newCard = moveToGraveyard(c, cause, null);
if (table != null) {
table.put(ZoneType.Battlefield, newCard.getZone().getZoneType(), newCard);
}
return newCard; return newCard;
} }

View File

@@ -51,7 +51,7 @@ public class ActivateAbilityEffect extends SpellAbilityEffect {
continue; continue;
} }
SpellAbility manaAb = p.getController().chooseSingleSpellForEffect( SpellAbility manaAb = p.getController().chooseSingleSpellForEffect(
possibleAb, sa, "Choose a mana ability:", ImmutableMap.of()); possibleAb, sa, "Choose a mana ability:", ImmutableMap.<String, Object>of());
p.getController().playChosenSpellAbility(manaAb); p.getController().playChosenSpellAbility(manaAb);
} }
} }

View File

@@ -5,6 +5,7 @@ import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardCollection; import forge.game.card.CardCollection;
import forge.game.card.CardLists; import forge.game.card.CardLists;
import forge.game.card.CardZoneTable;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
@@ -41,6 +42,7 @@ public class BalanceEffect extends SpellAbilityEffect {
min = Math.min(min, validCards.get(i).size()); min = Math.min(min, validCards.get(i).size());
} }
CardZoneTable table = new CardZoneTable();
for(int i = 0; i < players.size(); i++) { for(int i = 0; i < players.size(); i++) {
Player p = players.get(i); Player p = players.get(i);
int numToBalance = validCards.get(i).size() - min; int numToBalance = validCards.get(i).size() - min;
@@ -50,15 +52,16 @@ public class BalanceEffect extends SpellAbilityEffect {
if (zone.equals(ZoneType.Hand)) { if (zone.equals(ZoneType.Hand)) {
for (Card card : p.getController().chooseCardsToDiscardFrom(p, sa, validCards.get(i), numToBalance, numToBalance)) { for (Card card : p.getController().chooseCardsToDiscardFrom(p, sa, validCards.get(i), numToBalance, numToBalance)) {
if ( null == card ) continue; if ( null == card ) continue;
p.discard(card, sa); p.discard(card, sa, table);
} }
} else { // Battlefield } else { // Battlefield
// TODO: "can'e be sacrificed" // TODO: "can'e be sacrificed"
for(Card card : p.getController().choosePermanentsToSacrifice(sa, numToBalance, numToBalance, validCards.get(i), valid)) { for(Card card : p.getController().choosePermanentsToSacrifice(sa, numToBalance, numToBalance, validCards.get(i), valid)) {
if ( null == card ) continue; if ( null == card ) continue;
game.getAction().sacrifice(card, sa); game.getAction().sacrifice(card, sa, table);
} }
} }
} }
table.triggerChangesZoneAll(game);
} }
} }

View File

@@ -141,7 +141,7 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect {
} }
// movedCards should have same timestamp // movedCards should have same timestamp
long ts = game.getNextTimestamp(); long ts = game.getNextTimestamp();
final Map<ZoneType, CardCollection> triggerList = Maps.newEnumMap(ZoneType.class); final CardZoneTable triggerList = new CardZoneTable();
for (final Card c : cards) { for (final Card c : cards) {
final Zone originZone = game.getZoneOf(c); final Zone originZone = game.getZoneOf(c);
@@ -209,21 +209,13 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect {
} }
if (!movedCard.getZone().equals(originZone)) { if (!movedCard.getZone().equals(originZone)) {
if (!triggerList.containsKey(originZone.getZoneType())) { triggerList.put(originZone.getZoneType(), movedCard.getZone().getZoneType(), movedCard);
triggerList.put(originZone.getZoneType(), new CardCollection());
}
triggerList.get(originZone.getZoneType()).add(movedCard);
} }
} }
game.getTriggerHandler().resetActiveTriggers(false); game.getTriggerHandler().resetActiveTriggers(false);
if (!triggerList.isEmpty()) { triggerList.triggerChangesZoneAll(game);
final Map<String, Object> runParams = Maps.newHashMap();
runParams.put("Cards", triggerList);
runParams.put("Destination", destination);
game.getTriggerHandler().runTrigger(TriggerType.ChangesZoneAll, runParams, false);
}
// if Shuffle parameter exists, and any amount of cards were owned by // if Shuffle parameter exists, and any amount of cards were owned by
// that player, then shuffle that library // that player, then shuffle that library

View File

@@ -429,7 +429,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
final boolean optional = sa.hasParam("Optional"); final boolean optional = sa.hasParam("Optional");
final long ts = game.getNextTimestamp(); final long ts = game.getNextTimestamp();
final Map<ZoneType, CardCollection> triggerList = Maps.newEnumMap(ZoneType.class); final CardZoneTable triggerList = new CardZoneTable();
for (final Card tgtC : tgtCards) { for (final Card tgtC : tgtCards) {
if (tgt != null && tgtC.isInPlay() && !tgtC.canBeTargetedBy(sa)) { if (tgt != null && tgtC.isInPlay() && !tgtC.canBeTargetedBy(sa)) {
@@ -602,10 +602,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
} }
} }
if (!movedCard.getZone().equals(originZone)) { if (!movedCard.getZone().equals(originZone)) {
if (!triggerList.containsKey(originZone.getZoneType())) { triggerList.put(originZone.getZoneType(), movedCard.getZone().getZoneType(), movedCard);
triggerList.put(originZone.getZoneType(), new CardCollection());
}
triggerList.get(originZone.getZoneType()).add(movedCard);
if (remember != null) { if (remember != null) {
hostCard.addRemembered(movedCard); hostCard.addRemembered(movedCard);
@@ -619,12 +616,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
} }
} }
if (!triggerList.isEmpty()) { triggerList.triggerChangesZoneAll(game);
final Map<String, Object> runParams = Maps.newHashMap();
runParams.put("Cards", triggerList);
runParams.put("Destination", destination);
game.getTriggerHandler().runTrigger(TriggerType.ChangesZoneAll, runParams, false);
}
// for things like Gaea's Blessing // for things like Gaea's Blessing
if (destination.equals(ZoneType.Library) && sa.hasParam("Shuffle") && "True".equals(sa.getParam("Shuffle"))) { if (destination.equals(ZoneType.Library) && sa.hasParam("Shuffle") && "True".equals(sa.getParam("Shuffle"))) {
@@ -951,8 +943,8 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
CardCollection movedCards = new CardCollection(); CardCollection movedCards = new CardCollection();
long ts = game.getNextTimestamp(); long ts = game.getNextTimestamp();
final Map<ZoneType, CardCollection> triggerList = Maps.newEnumMap(ZoneType.class); final CardZoneTable triggerList = new CardZoneTable();
for (Card c : chosenCards) { for (final Card c : chosenCards) {
Card movedCard = null; Card movedCard = null;
final Zone originZone = game.getZoneOf(c); final Zone originZone = game.getZoneOf(c);
if (destination.equals(ZoneType.Library)) { if (destination.equals(ZoneType.Library)) {
@@ -1117,10 +1109,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
movedCards.add(movedCard); movedCards.add(movedCard);
if (originZone != null) { if (originZone != null) {
if (!triggerList.containsKey(originZone.getZoneType())) { triggerList.put(originZone.getZoneType(), movedCard.getZone().getZoneType(), movedCard);
triggerList.put(originZone.getZoneType(), new CardCollection());
}
triggerList.get(originZone.getZoneType()).add(movedCard);
} }
if (champion) { if (champion) {
@@ -1152,13 +1141,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
player.shuffle(sa); player.shuffle(sa);
} }
if (!triggerList.isEmpty()) { triggerList.triggerChangesZoneAll(game);
final Map<String, Object> runParams = Maps.newHashMap();
runParams.put("Cards", triggerList);
runParams.put("Destination", destination);
game.getTriggerHandler().runTrigger(TriggerType.ChangesZoneAll, runParams, false);
}
} }
private static boolean allowMultiSelect(Player decider, SpellAbility sa) { private static boolean allowMultiSelect(Player decider, SpellAbility sa) {
@@ -1231,24 +1214,4 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
} }
} }
} }
private static boolean checkCanIndirectlyAttachTo(final Card source, final Card target) {
final SpellAbility attachEff = source.getFirstAttachSpell();
if (attachEff == null) {
return false;
}
final Game game = source.getGame();
final TargetRestrictions tgt = attachEff.getTargetRestrictions();
Player attachEffCtrl = attachEff.getActivatingPlayer();
if (attachEffCtrl == null && attachEff.getHostCard() != null) {
attachEffCtrl = attachEff.getHostCard().getController();
}
CardCollectionView list = game.getCardsIn(tgt.getZone());
list = CardLists.getValidCards(list, tgt.getValidTgts(), attachEffCtrl, source, attachEff);
return list.contains(target);
}
} }

View File

@@ -206,12 +206,7 @@ public class ControlGainEffect extends SpellAbilityEffect {
final Ability ability = new Ability(hostCard, ManaCost.ZERO) { final Ability ability = new Ability(hostCard, ManaCost.ZERO) {
@Override @Override
public void resolve() { public void resolve() {
game.getAction().destroy(c, null, !bNoRegen, null);
if (bNoRegen) {
game.getAction().destroyNoRegeneration(c, null);
} else {
game.getAction().destroy(c, null);
}
} }
}; };
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();

View File

@@ -119,7 +119,7 @@ public class CounterEffect extends SpellAbilityEffect {
// Destroy Permanent may be able to be turned into a SubAbility // Destroy Permanent may be able to be turned into a SubAbility
if (tgtSA.isAbility() && sa.hasParam("DestroyPermanent")) { if (tgtSA.isAbility() && sa.hasParam("DestroyPermanent")) {
game.getAction().destroy(tgtSACard, sa); game.getAction().destroy(tgtSACard, sa, true, null);
} }
if (sa.hasParam("RememberCountered")) { if (sa.hasParam("RememberCountered")) {

View File

@@ -1,6 +1,5 @@
package forge.game.ability.effects; package forge.game.ability.effects;
import com.google.common.base.Predicate;
import forge.game.Game; import forge.game.Game;
import forge.game.GameActionUtil; import forge.game.GameActionUtil;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
@@ -8,7 +7,9 @@ import forge.game.ability.SpellAbilityEffect;
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.CardPredicates;
import forge.game.card.CardUtil; import forge.game.card.CardUtil;
import forge.game.card.CardZoneTable;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
@@ -78,30 +79,20 @@ public class DestroyAllEffect extends SpellAbilityEffect {
} }
// exclude cards that can't be destroyed at this moment // exclude cards that can't be destroyed at this moment
list = CardLists.filter(list, new Predicate<Card>() { list = CardLists.filter(list, CardPredicates.Presets.CAN_BE_DESTROYED);
@Override
public boolean apply(Card card) {
return card.canBeDestroyed();
}
});
if (list.size() > 1) { if (list.size() > 1) {
list = GameActionUtil.orderCardsByTheirOwners(game, list, ZoneType.Graveyard); list = GameActionUtil.orderCardsByTheirOwners(game, list, ZoneType.Graveyard);
} }
if (noRegen) { CardZoneTable table = new CardZoneTable();
for (Card c : list) {
if (game.getAction().destroyNoRegeneration(c, sa) && remDestroyed) { for (Card c : list) {
card.addRemembered(CardUtil.getLKICopy(c)); if (game.getAction().destroy(c, sa, !noRegen, table) && remDestroyed) {
} card.addRemembered(CardUtil.getLKICopy(c));
}
} else {
for (Card c : list) {
if (game.getAction().destroy(c, sa) && remDestroyed) {
card.addRemembered(CardUtil.getLKICopy(c));
}
} }
} }
table.triggerChangesZoneAll(game);
} }
} }

View File

@@ -6,8 +6,8 @@ import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardCollection; import forge.game.card.CardCollection;
import forge.game.card.CardUtil; import forge.game.card.CardUtil;
import forge.game.card.CardZoneTable;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import java.util.Iterator; import java.util.Iterator;
@@ -77,8 +77,6 @@ public class DestroyEffect extends SpellAbilityEffect {
CardCollection tgtCards = getTargetCards(sa); CardCollection tgtCards = getTargetCards(sa);
CardCollection untargetedCards = new CardCollection(); CardCollection untargetedCards = new CardCollection();
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (sa.hasParam("Radiance")) { if (sa.hasParam("Radiance")) {
for (final Card c : CardUtil.getRadiance(card, tgtCards.get(0), for (final Card c : CardUtil.getRadiance(card, tgtCards.get(0),
sa.getParam("ValidTgts").split(","))) { sa.getParam("ValidTgts").split(","))) {
@@ -90,19 +88,18 @@ public class DestroyEffect extends SpellAbilityEffect {
tgtCards = (CardCollection) GameActionUtil.orderCardsByTheirOwners(game, tgtCards, ZoneType.Graveyard); tgtCards = (CardCollection) GameActionUtil.orderCardsByTheirOwners(game, tgtCards, ZoneType.Graveyard);
} }
CardZoneTable table = new CardZoneTable();
for (final Card tgtC : tgtCards) { for (final Card tgtC : tgtCards) {
if (tgtC.isInPlay() && ((tgt == null) || tgtC.canBeTargetedBy(sa))) { if (tgtC.isInPlay() && (!sa.usesTargeting() || tgtC.canBeTargetedBy(sa))) {
boolean destroyed = false; boolean destroyed = false;
final Card lki = CardUtil.getLKICopy(tgtC); final Card lki = CardUtil.getLKICopy(tgtC);
if (remAttached) { if (remAttached) {
card.addRemembered(tgtC.getAttachedCards()); card.addRemembered(tgtC.getAttachedCards());
} }
if (sac) { if (sac) {
destroyed = game.getAction().sacrifice(tgtC, sa) != null; destroyed = game.getAction().sacrifice(tgtC, sa, table) != null;
} else if (noRegen) {
destroyed = game.getAction().destroyNoRegeneration(tgtC, sa);
} else { } else {
destroyed = game.getAction().destroy(tgtC, sa); destroyed = game.getAction().destroy(tgtC, sa, !noRegen, table);
} }
if (destroyed && remDestroyed) { if (destroyed && remDestroyed) {
card.addRemembered(tgtC); card.addRemembered(tgtC);
@@ -121,16 +118,16 @@ public class DestroyEffect extends SpellAbilityEffect {
if (unTgtC.isInPlay()) { if (unTgtC.isInPlay()) {
boolean destroyed = false; boolean destroyed = false;
if (sac) { if (sac) {
destroyed = game.getAction().sacrifice(unTgtC, sa) != null; destroyed = game.getAction().sacrifice(unTgtC, sa, table) != null;
} else if (noRegen) {
destroyed = game.getAction().destroyNoRegeneration(unTgtC, sa);
} else { } else {
destroyed = game.getAction().destroy(unTgtC, sa); destroyed = game.getAction().destroy(unTgtC, sa, !noRegen, table);
} if (destroyed && remDestroyed) { } if (destroyed && remDestroyed) {
card.addRemembered(unTgtC); card.addRemembered(unTgtC);
} }
} }
} }
table.triggerChangesZoneAll(game);
} }
} }

View File

@@ -9,6 +9,7 @@ import forge.game.card.Card;
import forge.game.card.CardCollection; 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.card.CardZoneTable;
import forge.game.card.CounterType; import forge.game.card.CounterType;
import forge.game.player.DelayedReveal; import forge.game.player.DelayedReveal;
import forge.game.player.Player; import forge.game.player.Player;
@@ -109,6 +110,7 @@ public class DigEffect extends SpellAbilityEffect {
} }
} }
CardZoneTable table = new CardZoneTable();
for (final Player p : tgtPlayers) { for (final Player p : tgtPlayers) {
if (tgt != null && !p.canBeTargetedBy(sa)) { if (tgt != null && !p.canBeTargetedBy(sa)) {
continue; continue;
@@ -301,6 +303,7 @@ public class DigEffect extends SpellAbilityEffect {
effectHost = sa.getHostCard(); effectHost = sa.getHostCard();
} }
for (Card c : movedCards) { for (Card c : movedCards) {
final ZoneType origin = c.getZone().getZoneType();
final PlayerZone zone = c.getOwner().getZone(destZone1); final PlayerZone zone = c.getOwner().getZone(destZone1);
if (zone.is(ZoneType.Library) || zone.is(ZoneType.PlanarDeck) || zone.is(ZoneType.SchemeDeck)) { if (zone.is(ZoneType.Library) || zone.is(ZoneType.PlanarDeck) || zone.is(ZoneType.SchemeDeck)) {
@@ -322,6 +325,9 @@ public class DigEffect extends SpellAbilityEffect {
c.setExiledWith(effectHost); c.setExiledWith(effectHost);
} }
} }
if (!origin.equals(c.getZone().getZoneType())) {
table.put(origin, c.getZone().getZoneType(), c);
}
if (sa.hasParam("ExileFaceDown")) { if (sa.hasParam("ExileFaceDown")) {
c.setState(CardStateName.FaceDown, true); c.setState(CardStateName.FaceDown, true);
@@ -357,11 +363,16 @@ public class DigEffect extends SpellAbilityEffect {
Collections.reverse(afterOrder); Collections.reverse(afterOrder);
} }
for (final Card c : afterOrder) { for (final Card c : afterOrder) {
final ZoneType origin = c.getZone().getZoneType();
Card m;
if (destZone2 == ZoneType.Library) { if (destZone2 == ZoneType.Library) {
game.getAction().moveToLibrary(c, libraryPosition2, sa); m = game.getAction().moveToLibrary(c, libraryPosition2, sa);
} }
else { else {
game.getAction().moveToVariantDeck(c, destZone2, libraryPosition2, sa); m = game.getAction().moveToVariantDeck(c, destZone2, libraryPosition2, sa);
}
if (m != null && !origin.equals(m.getZone().getZoneType())) {
table.put(origin, m.getZone().getZoneType(), m);
} }
} }
} }
@@ -369,8 +380,12 @@ public class DigEffect extends SpellAbilityEffect {
// just move them randomly // just move them randomly
for (int i = 0; i < rest.size(); i++) { for (int i = 0; i < rest.size(); i++) {
Card c = rest.get(i); Card c = rest.get(i);
final ZoneType origin = c.getZone().getZoneType();
final PlayerZone toZone = c.getOwner().getZone(destZone2); final PlayerZone toZone = c.getOwner().getZone(destZone2);
c = game.getAction().moveTo(toZone, c, sa); c = game.getAction().moveTo(toZone, c, sa);
if (!origin.equals(c.getZone().getZoneType())) {
table.put(origin, c.getZone().getZoneType(), c);
}
if (destZone2 == ZoneType.Battlefield && !keywords.isEmpty()) { if (destZone2 == ZoneType.Battlefield && !keywords.isEmpty()) {
for (final String kw : keywords) { for (final String kw : keywords) {
c.addExtrinsicKeyword(kw); c.addExtrinsicKeyword(kw);
@@ -386,6 +401,8 @@ public class DigEffect extends SpellAbilityEffect {
} }
} }
} }
//table trigger there
table.triggerChangesZoneAll(game);
} }
// TODO This should be somewhere else, maybe like CardUtil or something like that // TODO This should be somewhere else, maybe like CardUtil or something like that

View File

@@ -5,9 +5,9 @@ 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.CardCollection;
import forge.game.card.CardZoneTable;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.PlayerZone; import forge.game.zone.PlayerZone;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.util.MyRandom; import forge.util.MyRandom;
@@ -76,6 +76,7 @@ public class DigUntilEffect extends SpellAbilityEffect {
@Override @Override
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();
String[] type = new String[]{"Card"}; String[] type = new String[]{"Card"};
if (sa.hasParam("Valid")) { if (sa.hasParam("Valid")) {
@@ -94,8 +95,6 @@ public class DigUntilEffect extends SpellAbilityEffect {
final boolean remember = sa.hasParam("RememberFound"); final boolean remember = sa.hasParam("RememberFound");
final TargetRestrictions tgt = sa.getTargetRestrictions();
final ZoneType foundDest = ZoneType.smartValueOf(sa.getParam("FoundDestination")); final ZoneType foundDest = ZoneType.smartValueOf(sa.getParam("FoundDestination"));
final int foundLibPos = AbilityUtils.calculateAmount(host, sa.getParam("FoundLibraryPosition"), sa); final int foundLibPos = AbilityUtils.calculateAmount(host, sa.getParam("FoundLibraryPosition"), sa);
final ZoneType revealedDest = ZoneType.smartValueOf(sa.getParam("RevealedDestination")); final ZoneType revealedDest = ZoneType.smartValueOf(sa.getParam("RevealedDestination"));
@@ -107,11 +106,13 @@ public class DigUntilEffect extends SpellAbilityEffect {
final boolean optional = sa.hasParam("Optional"); final boolean optional = sa.hasParam("Optional");
final boolean optionalFound = sa.hasParam("OptionalFoundMove"); final boolean optionalFound = sa.hasParam("OptionalFoundMove");
CardZoneTable table = new CardZoneTable();
for (final Player p : getTargetPlayers(sa)) { for (final Player p : getTargetPlayers(sa)) {
if (p == null) { if (p == null) {
continue; continue;
} }
if ((tgt == null) || p.canBeTargetedBy(sa)) { if (!sa.usesTargeting() || p.canBeTargetedBy(sa)) {
if (optional && !p.getController().confirmAction(sa, null, "Do you want to dig your library?")) { if (optional && !p.getController().confirmAction(sa, null, "Do you want to dig your library?")) {
continue; continue;
} }
@@ -142,7 +143,6 @@ public class DigUntilEffect extends SpellAbilityEffect {
} }
} }
final Game game = p.getGame();
if (revealed.size() > 0) { if (revealed.size() > 0) {
game.getAction().reveal(revealed, p, false); game.getAction().reveal(revealed, p, false);
} }
@@ -157,19 +157,24 @@ public class DigUntilEffect extends SpellAbilityEffect {
final Iterator<Card> itr = found.iterator(); final Iterator<Card> itr = found.iterator();
while (itr.hasNext()) { while (itr.hasNext()) {
final Card c = itr.next(); final Card c = itr.next();
final ZoneType origin = c.getZone().getZoneType();
if (optionalFound && !p.getController().confirmAction(sa, null, if (optionalFound && !p.getController().confirmAction(sa, null,
"Do you want to put that card to " + foundDest.name() + "?")) { "Do you want to put that card to " + foundDest.name() + "?")) {
continue; continue;
} else { } else {
Card m = null;
if (sa.hasParam("GainControl") && foundDest.equals(ZoneType.Battlefield)) { if (sa.hasParam("GainControl") && foundDest.equals(ZoneType.Battlefield)) {
c.setController(sa.getActivatingPlayer(), game.getNextTimestamp()); c.setController(sa.getActivatingPlayer(), game.getNextTimestamp());
game.getAction().moveTo(c.getController().getZone(foundDest), c, sa); m = game.getAction().moveTo(c.getController().getZone(foundDest), c, sa);
} else if (sa.hasParam("NoMoveFound") && foundDest.equals(ZoneType.Library)) { } else if (sa.hasParam("NoMoveFound") && foundDest.equals(ZoneType.Library)) {
//Don't do anything //Don't do anything
} else { } else {
game.getAction().moveTo(foundDest, c, foundLibPos, sa); m = game.getAction().moveTo(foundDest, c, foundLibPos, sa);
} }
revealed.remove(c); revealed.remove(c);
if (m != null && !origin.equals(m.getZone().getZoneType())) {
table.put(origin, m.getZone().getZoneType(), m);
}
} }
} }
} }
@@ -201,7 +206,11 @@ public class DigUntilEffect extends SpellAbilityEffect {
final Iterator<Card> itr = revealed.iterator(); final Iterator<Card> itr = revealed.iterator();
while (itr.hasNext()) { while (itr.hasNext()) {
final Card c = itr.next(); final Card c = itr.next();
game.getAction().moveTo(noneFoundDest, c, noneFoundLibPos, sa); final ZoneType origin = c.getZone().getZoneType();
final Card m = game.getAction().moveTo(noneFoundDest, c, noneFoundLibPos, sa);
if (m != null && !origin.equals(m.getZone().getZoneType())) {
table.put(origin, m.getZone().getZoneType(), m);
}
} }
} else { } else {
// Allow ordering the rest of the revealed cards // Allow ordering the rest of the revealed cards
@@ -216,7 +225,11 @@ public class DigUntilEffect extends SpellAbilityEffect {
final Iterator<Card> itr = revealed.iterator(); final Iterator<Card> itr = revealed.iterator();
while (itr.hasNext()) { while (itr.hasNext()) {
final Card c = itr.next(); final Card c = itr.next();
game.getAction().moveTo(revealedDest, c, revealedLibPos, sa); final ZoneType origin = c.getZone().getZoneType();
final Card m = game.getAction().moveTo(revealedDest, c, revealedLibPos, sa);
if (m != null && !origin.equals(m.getZone().getZoneType())) {
table.put(origin, m.getZone().getZoneType(), m);
}
} }
} }
@@ -225,6 +238,7 @@ public class DigUntilEffect extends SpellAbilityEffect {
} }
} // end foreach player } // end foreach player
} }
table.triggerChangesZoneAll(game);
} // end resolve } // end resolve
} }

View File

@@ -1,7 +1,5 @@
package forge.game.ability.effects; package forge.game.ability.effects;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import forge.game.GameActionUtil; import forge.game.GameActionUtil;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect; import forge.game.ability.SpellAbilityEffect;
@@ -10,12 +8,17 @@ import forge.game.card.CardPredicates.Presets;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode; import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.util.Lang;
import forge.util.Aggregates; import forge.util.Aggregates;
import forge.util.TextUtil; import forge.util.TextUtil;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -29,10 +32,7 @@ public class DiscardEffect extends SpellAbilityEffect {
final List<Player> tgtPlayers = getTargetPlayers(sa); final List<Player> tgtPlayers = getTargetPlayers(sa);
if (!tgtPlayers.isEmpty()) { if (!tgtPlayers.isEmpty()) {
sb.append(Lang.joinHomogenous(tgtPlayers)).append(" ");
for (final Player p : tgtPlayers) {
sb.append(p.toString()).append(" ");
}
if (mode.equals("RevealYouChoose")) { if (mode.equals("RevealYouChoose")) {
sb.append("reveals their hand.").append(" You choose ("); sb.append("reveals their hand.").append(" You choose (");
@@ -105,8 +105,6 @@ public class DiscardEffect extends SpellAbilityEffect {
final String mode = sa.getParam("Mode"); final String mode = sa.getParam("Mode");
//final boolean anyNumber = sa.hasParam("AnyNumber"); //final boolean anyNumber = sa.hasParam("AnyNumber");
final TargetRestrictions tgt = sa.getTargetRestrictions();
final List<Card> discarded = new ArrayList<Card>(); final List<Card> discarded = new ArrayList<Card>();
final List<Player> targets = getTargetPlayers(sa), final List<Player> targets = getTargetPlayers(sa),
discarders; discarders;
@@ -115,7 +113,7 @@ public class DiscardEffect extends SpellAbilityEffect {
// In this case the target need not be the discarding player // In this case the target need not be the discarding player
discarders = getDefinedPlayersOrTargeted(sa); discarders = getDefinedPlayersOrTargeted(sa);
firstTarget = Iterables.getFirst(targets, null); firstTarget = Iterables.getFirst(targets, null);
if (tgt != null && !firstTarget.canBeTargetedBy(sa)) { if (sa.usesTargeting() && !firstTarget.canBeTargetedBy(sa)) {
firstTarget = null; firstTarget = null;
} }
} else { } else {
@@ -123,8 +121,9 @@ public class DiscardEffect extends SpellAbilityEffect {
} }
final CardZoneTable table = new CardZoneTable();
for (final Player p : discarders) { for (final Player p : discarders) {
if ((mode.equals("RevealTgtChoose") && firstTarget != null) || tgt == null || p.canBeTargetedBy(sa)) { if ((mode.equals("RevealTgtChoose") && firstTarget != null) || !sa.usesTargeting() || p.canBeTargetedBy(sa)) {
if (sa.hasParam("RememberDiscarder")) { if (sa.hasParam("RememberDiscarder")) {
source.addRemembered(p); source.addRemembered(p);
} }
@@ -140,7 +139,7 @@ public class DiscardEffect extends SpellAbilityEffect {
} }
for (final Card c : toDiscard) { for (final Card c : toDiscard) {
boolean hasDiscarded = p.discard(c, sa) != null; boolean hasDiscarded = p.discard(c, sa, table) != null;
if (hasDiscarded) { if (hasDiscarded) {
discarded.add(c); discarded.add(c);
} }
@@ -164,7 +163,7 @@ public class DiscardEffect extends SpellAbilityEffect {
} }
for(Card c : toDiscard) { // without copying will get concurrent modification exception for(Card c : toDiscard) { // without copying will get concurrent modification exception
boolean hasDiscarded = p.discard(c, sa) != null; boolean hasDiscarded = p.discard(c, sa, table) != null;
if( hasDiscarded && shouldRemember ) if( hasDiscarded && shouldRemember )
source.addRemembered(c); source.addRemembered(c);
} }
@@ -178,7 +177,7 @@ public class DiscardEffect extends SpellAbilityEffect {
} }
for (final Card c : dPHand) { for (final Card c : dPHand) {
p.discard(c, sa); p.discard(c, sa, table);
discarded.add(c); discarded.add(c);
} }
} }
@@ -216,7 +215,7 @@ public class DiscardEffect extends SpellAbilityEffect {
} }
for (Card c : toDiscardView) { for (Card c : toDiscardView) {
if (p.discard(c, sa) != null) { if (p.discard(c, sa, table) != null) {
discarded.add(c); discarded.add(c);
} }
} }
@@ -233,7 +232,7 @@ public class DiscardEffect extends SpellAbilityEffect {
} }
for (Card c : toDiscard) { for (Card c : toDiscard) {
c.getController().discard(c, sa); c.getController().discard(c, sa, table);
} }
} }
} }
@@ -260,7 +259,7 @@ public class DiscardEffect extends SpellAbilityEffect {
// Reveal cards that will be discarded? // Reveal cards that will be discarded?
for (final Card c : dPChHand) { for (final Card c : dPChHand) {
p.discard(c, sa); p.discard(c, sa, table);
discarded.add(c); discarded.add(c);
} }
} else if (mode.equals("RevealYouChoose") || mode.equals("RevealTgtChoose") || mode.equals("TgtChoose")) { } else if (mode.equals("RevealYouChoose") || mode.equals("RevealTgtChoose") || mode.equals("TgtChoose")) {
@@ -304,7 +303,7 @@ public class DiscardEffect extends SpellAbilityEffect {
} }
for (Card card : toBeDiscarded) { for (Card card : toBeDiscarded) {
if (card == null) { continue; } if (card == null) { continue; }
p.discard(card, sa); p.discard(card, sa, table);
discarded.add(card); discarded.add(card);
} }
} }
@@ -317,5 +316,8 @@ public class DiscardEffect extends SpellAbilityEffect {
source.addRemembered(c); source.addRemembered(c);
} }
} }
// run trigger if something got milled
table.triggerChangesZoneAll(source.getGame());
} // discardResolve() } // discardResolve()
} }

View File

@@ -1,23 +1,25 @@
package forge.game.ability.effects; package forge.game.ability.effects;
import forge.card.CardStateName; import forge.card.CardStateName;
import forge.game.Game;
import forge.game.GameLogEntryType; import forge.game.GameLogEntryType;
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.CardCollectionView; import forge.game.card.CardCollectionView;
import forge.game.card.CardZoneTable;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.util.TextUtil;
import java.util.List; import forge.util.Lang;
import forge.util.TextUtil;
public class MillEffect extends SpellAbilityEffect { public class MillEffect extends SpellAbilityEffect {
@Override @Override
public void resolve(SpellAbility sa) { public void resolve(SpellAbility sa) {
final Card source = sa.getHostCard(); final Card source = sa.getHostCard();
final Game game = source.getGame();
final int numCards = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumCards"), sa); final int numCards = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumCards"), sa);
final boolean bottom = sa.hasParam("FromBottom"); final boolean bottom = sa.hasParam("FromBottom");
final boolean facedown = sa.hasParam("ExileFaceDown"); final boolean facedown = sa.hasParam("ExileFaceDown");
@@ -28,27 +30,27 @@ public class MillEffect extends SpellAbilityEffect {
source.clearRemembered(); source.clearRemembered();
} }
final TargetRestrictions tgt = sa.getTargetRestrictions();
ZoneType destination = ZoneType.smartValueOf(sa.getParam("Destination")); ZoneType destination = ZoneType.smartValueOf(sa.getParam("Destination"));
if (destination == null) { if (destination == null) {
destination = ZoneType.Graveyard; destination = ZoneType.Graveyard;
} }
final CardZoneTable table = new CardZoneTable();
for (final Player p : getTargetPlayers(sa)) { for (final Player p : getTargetPlayers(sa)) {
if ((tgt == null) || p.canBeTargetedBy(sa)) { if (!sa.usesTargeting() || p.canBeTargetedBy(sa)) {
if (sa.hasParam("Optional")) { if (sa.hasParam("Optional")) {
final String prompt = TextUtil.concatWithSpace("Do you want to put card(s) from library to", TextUtil.addSuffix(destination.toString(),"?")); final String prompt = TextUtil.concatWithSpace("Do you want to put card(s) from library to", TextUtil.addSuffix(destination.toString(),"?"));
if (!p.getController().confirmAction(sa, null, prompt)) { if (!p.getController().confirmAction(sa, null, prompt)) {
continue; continue;
} }
} }
final CardCollectionView milled = p.mill(numCards, destination, bottom); final CardCollectionView milled = p.mill(numCards, destination, bottom, sa, table);
// Reveal the milled cards, so players don't have to manually inspect the // Reveal the milled cards, so players don't have to manually inspect the
// graveyard to figure out which ones were milled. // graveyard to figure out which ones were milled.
if (!facedown && reveal) { // do not reveal when exiling face down if (!facedown && reveal) { // do not reveal when exiling face down
if (showRevealDialog) { if (showRevealDialog) {
p.getGame().getAction().reveal(milled, p, false); game.getAction().reveal(milled, p, false);
} }
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append(p).append(" milled ").append(milled).append(" to ").append(destination); sb.append(p).append(" milled ").append(milled).append(" to ").append(destination);
@@ -78,6 +80,9 @@ public class MillEffect extends SpellAbilityEffect {
} }
} }
} }
// run trigger if something got milled
table.triggerChangesZoneAll(game);
} }
@Override @Override
@@ -85,10 +90,7 @@ public class MillEffect extends SpellAbilityEffect {
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();
final int numCards = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumCards"), sa); final int numCards = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumCards"), sa);
final List<Player> tgtPlayers = getTargetPlayers(sa); sb.append(Lang.joinHomogenous(getTargetPlayers(sa))).append(" ");
for (final Player p : tgtPlayers) {
sb.append(p.toString()).append(" ");
}
final ZoneType dest = ZoneType.smartValueOf(sa.getParam("Destination")); final ZoneType dest = ZoneType.smartValueOf(sa.getParam("Destination"));
if ((dest == null) || dest.equals(ZoneType.Graveyard)) { if ((dest == null) || dest.equals(ZoneType.Graveyard)) {

View File

@@ -63,12 +63,14 @@ public class SacrificeAllEffect extends SpellAbilityEffect {
list = GameActionUtil.orderCardsByTheirOwners(game, list, ZoneType.Graveyard); list = GameActionUtil.orderCardsByTheirOwners(game, list, ZoneType.Graveyard);
} }
CardZoneTable table = new CardZoneTable();
for (Card sac : list) { for (Card sac : list) {
final Card lKICopy = CardUtil.getLKICopy(sac); final Card lKICopy = CardUtil.getLKICopy(sac);
if (game.getAction().sacrifice(sac, sa) != null && remSacrificed) { if (game.getAction().sacrifice(sac, sa, table) != null && remSacrificed) {
card.addRemembered(lKICopy); card.addRemembered(lKICopy);
} }
} }
table.triggerChangesZoneAll(game);
} }
} }

View File

@@ -91,14 +91,15 @@ public class SacrificeEffect extends SpellAbilityEffect {
final boolean remSacrificed = sa.hasParam("RememberSacrificed"); final boolean remSacrificed = sa.hasParam("RememberSacrificed");
final String remSVar = sa.getParam("RememberSacrificedSVar"); final String remSVar = sa.getParam("RememberSacrificedSVar");
int countSacrificed = 0; int countSacrificed = 0;
CardZoneTable table = new CardZoneTable();
if (valid.equals("Self") && game.getZoneOf(card) != null) { if (valid.equals("Self") && game.getZoneOf(card) != null) {
if (game.getZoneOf(card).is(ZoneType.Battlefield)) { if (game.getZoneOf(card).is(ZoneType.Battlefield)) {
if (game.getAction().sacrifice(card, sa) != null) { if (game.getAction().sacrifice(card, sa, table) != null) {
countSacrificed++; countSacrificed++;
if (remSacrificed) { if (remSacrificed) {
card.addRemembered(card); card.addRemembered(card);
} }
} }
} }
} }
@@ -135,8 +136,8 @@ public class SacrificeEffect extends SpellAbilityEffect {
for (Card sac : choosenToSacrifice) { for (Card sac : choosenToSacrifice) {
final Card lKICopy = CardUtil.getLKICopy(sac); final Card lKICopy = CardUtil.getLKICopy(sac);
boolean wasSacrificed = !destroy && game.getAction().sacrifice(sac, sa) != null; boolean wasSacrificed = !destroy && game.getAction().sacrifice(sac, sa, table) != null;
boolean wasDestroyed = destroy && game.getAction().destroy(sac, sa); boolean wasDestroyed = destroy && game.getAction().destroy(sac, sa, true, table);
// Run Devour Trigger // Run Devour Trigger
if (devour) { if (devour) {
card.addDevoured(lKICopy); card.addDevoured(lKICopy);
@@ -168,6 +169,8 @@ public class SacrificeEffect extends SpellAbilityEffect {
} while (root != null); } while (root != null);
} }
} }
table.triggerChangesZoneAll(game);
} }
@Override @Override

View File

@@ -219,6 +219,7 @@ public final class CardUtil {
newCopy.setSetCode(in.getSetCode()); newCopy.setSetCode(in.getSetCode());
newCopy.setOwner(in.getOwner()); newCopy.setOwner(in.getOwner());
newCopy.setController(in.getController(), 0); newCopy.setController(in.getController(), 0);
newCopy.setCommander(in.isCommander());
// needed to ensure that the LKI object has correct CMC info no matter what state the original card was in // needed to ensure that the LKI object has correct CMC info no matter what state the original card was in
// (e.g. Scrap Trawler + transformed Harvest Hand) // (e.g. Scrap Trawler + transformed Harvest Hand)

View File

@@ -0,0 +1,48 @@
/**
*
*/
package forge.game.card;
import java.util.Map;
import com.google.common.collect.ForwardingTable;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Maps;
import com.google.common.collect.Table;
import forge.game.Game;
import forge.game.trigger.TriggerType;
import forge.game.zone.ZoneType;
public class CardZoneTable extends ForwardingTable<ZoneType, ZoneType, CardCollection> {
// TODO use EnumBasedTable if exist
private Table<ZoneType, ZoneType, CardCollection> dataMap = HashBasedTable.create();
/**
* special put logic, add Card to Card Collection
*/
public CardCollection put(ZoneType rowKey, ZoneType columnKey, Card value) {
CardCollection old;
if (contains(rowKey, columnKey)) {
old = get(rowKey, columnKey);
old.add(value);
} else {
old = new CardCollection(value);
dataMap.put(rowKey, columnKey, old);
}
return old;
}
@Override
protected Table<ZoneType, ZoneType, CardCollection> delegate() {
return dataMap;
}
public void triggerChangesZoneAll(final Game game) {
if (!isEmpty()) {
final Map<String, Object> runParams = Maps.newHashMap();
runParams.put("Cards", this);
game.getTriggerHandler().runTrigger(TriggerType.ChangesZoneAll, runParams, false);
}
}
}

View File

@@ -1,7 +1,5 @@
package forge.game.cost; package forge.game.cost;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import forge.card.CardStateName; import forge.card.CardStateName;
import forge.card.mana.ManaAtom; import forge.card.mana.ManaAtom;
import forge.card.mana.ManaCost; import forge.card.mana.ManaCost;
@@ -19,14 +17,15 @@ import forge.game.spellability.Spell;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetChoices; import forge.game.spellability.TargetChoices;
import forge.game.staticability.StaticAbility; import forge.game.staticability.StaticAbility;
import forge.game.trigger.TriggerType;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import org.apache.commons.lang3.StringUtils;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import com.google.common.collect.Lists;
import org.apache.commons.lang3.StringUtils;
public class CostAdjustment { public class CostAdjustment {
public static Cost adjust(final Cost cost, final SpellAbility sa) { public static Cost adjust(final Cost cost, final SpellAbility sa) {
@@ -214,7 +213,7 @@ public class CostAdjustment {
if (sa.getHostCard().hasKeyword(Keyword.DELVE)) { if (sa.getHostCard().hasKeyword(Keyword.DELVE)) {
sa.getHostCard().clearDelved(); sa.getHostCard().clearDelved();
final CardCollection delved = new CardCollection(); final CardZoneTable table = new CardZoneTable();
final Player pc = sa.getActivatingPlayer(); final Player pc = sa.getActivatingPlayer();
final CardCollection mutableGrave = new CardCollection(pc.getCardsIn(ZoneType.Graveyard)); final CardCollection mutableGrave = new CardCollection(pc.getCardsIn(ZoneType.Graveyard));
final CardCollectionView toExile = pc.getController().chooseCardsToDelve(cost.getUnpaidShards(ManaCostShard.GENERIC), mutableGrave); final CardCollectionView toExile = pc.getController().chooseCardsToDelve(cost.getUnpaidShards(ManaCostShard.GENERIC), mutableGrave);
@@ -224,17 +223,11 @@ public class CostAdjustment {
cardsToDelveOut.add(c); cardsToDelveOut.add(c);
} else if (!test) { } else if (!test) {
sa.getHostCard().addDelved(c); sa.getHostCard().addDelved(c);
delved.add(game.getAction().exile(c, null, null)); final Card d = game.getAction().exile(c, null, null);
table.put(ZoneType.Graveyard, d.getZone().getZoneType(), d);
} }
} }
if (!delved.isEmpty()) { table.triggerChangesZoneAll(game);
final Map<ZoneType, CardCollection> triggerList = Maps.newEnumMap(ZoneType.class);
triggerList.put(ZoneType.Graveyard, delved);
final Map<String, Object> runParams = Maps.newHashMap();
runParams.put("Cards", triggerList);
runParams.put("Destination", ZoneType.Exile);
game.getTriggerHandler().runTrigger(TriggerType.ChangesZoneAll, runParams, false);
}
} }
if (sa.getHostCard().hasKeyword(Keyword.CONVOKE)) { if (sa.getHostCard().hasKeyword(Keyword.CONVOKE)) {
adjustCostByConvokeOrImprovise(cost, sa, false, test); adjustCostByConvokeOrImprovise(cost, sa, false, test);

View File

@@ -164,7 +164,7 @@ public class CostDiscard extends CostPartWithList {
*/ */
@Override @Override
protected Card doPayment(SpellAbility ability, Card targetCard) { protected Card doPayment(SpellAbility ability, Card targetCard) {
return targetCard.getController().discard(targetCard, ability); return targetCard.getController().discard(targetCard, ability, null);
} }
/* (non-Javadoc) /* (non-Javadoc)

View File

@@ -21,8 +21,10 @@ import forge.game.card.Card;
import forge.game.card.CardCollection; import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView; import forge.game.card.CardCollectionView;
import forge.game.card.CardUtil; import forge.game.card.CardUtil;
import forge.game.card.CardZoneTable;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.zone.Zone;
/** /**
* The Class CostPartWithList. * The Class CostPartWithList.
@@ -35,6 +37,8 @@ public abstract class CostPartWithList extends CostPart {
/** The lists: one for LKI, one for the actual cards. */ /** The lists: one for LKI, one for the actual cards. */
private final CardCollection lkiList = new CardCollection(); private final CardCollection lkiList = new CardCollection();
protected final CardCollection cardList = new CardCollection(); protected final CardCollection cardList = new CardCollection();
protected final CardZoneTable table = new CardZoneTable();
// set is here because executePayment() adds card to list, while ai's decide payment does the same thing. // set is here because executePayment() adds card to list, while ai's decide payment does the same thing.
// set allows to avoid duplication // set allows to avoid duplication
@@ -52,6 +56,7 @@ public abstract class CostPartWithList extends CostPart {
public final void resetLists() { public final void resetLists() {
lkiList.clear(); lkiList.clear();
cardList.clear(); cardList.clear();
table.clear();
} }
/** /**
@@ -97,13 +102,21 @@ public abstract class CostPartWithList extends CostPart {
public final boolean executePayment(SpellAbility ability, Card targetCard) { public final boolean executePayment(SpellAbility ability, Card targetCard) {
lkiList.add(CardUtil.getLKICopy(targetCard)); lkiList.add(CardUtil.getLKICopy(targetCard));
final Zone origin = targetCard.getZone();
final Card newCard = doPayment(ability, targetCard); final Card newCard = doPayment(ability, targetCard);
cardList.add(newCard);
// need to update the LKI info to ensure correct interaction with cards which may trigger on this // need to update the LKI info to ensure correct interaction with cards which may trigger on this
// (e.g. Necroskitter + a creature dying from a -1/-1 counter on a cost payment). // (e.g. Necroskitter + a creature dying from a -1/-1 counter on a cost payment).
targetCard.getGame().updateLastStateForCard(targetCard); targetCard.getGame().updateLastStateForCard(targetCard);
if (newCard != null) {
final Zone newZone = newCard.getZone();
cardList.add(newCard);
if (!origin.equals(newZone)) {
table.put(origin.getZoneType(), newZone.getZoneType(), newCard);
}
}
return true; return true;
} }
@@ -112,11 +125,13 @@ public abstract class CostPartWithList extends CostPart {
if (canPayListAtOnce()) { // This is used by reveal. Without it when opponent would reveal hand, you'll get N message boxes. if (canPayListAtOnce()) { // This is used by reveal. Without it when opponent would reveal hand, you'll get N message boxes.
lkiList.addAll(targetCards); lkiList.addAll(targetCards);
cardList.addAll(doListPayment(ability, targetCards)); cardList.addAll(doListPayment(ability, targetCards));
handleChangeZoneTrigger(ability);
return true; return true;
} }
for (Card c: targetCards) { for (Card c: targetCards) {
executePayment(ability, c); executePayment(ability, c);
} }
handleChangeZoneTrigger(ability);
return true; return true;
} }
@@ -145,4 +160,15 @@ public abstract class CostPartWithList extends CostPart {
return true; return true;
} }
protected void handleChangeZoneTrigger(SpellAbility ability) {
if (table.isEmpty()) {
return;
}
// copy table because the original get cleaned after the cost is done
final CardZoneTable copyTable = new CardZoneTable();
copyTable.putAll(table);
copyTable.triggerChangesZoneAll(ability.getHostCard().getGame());
}
} }

View File

@@ -116,7 +116,8 @@ public class CostSacrifice extends CostPartWithList {
@Override @Override
protected Card doPayment(SpellAbility ability, Card targetCard) { protected Card doPayment(SpellAbility ability, Card targetCard) {
return targetCard.getGame().getAction().sacrifice(targetCard, ability); // no table there, it is already handled by CostPartWithList
return targetCard.getGame().getAction().sacrifice(targetCard, ability, null);
} }
/* (non-Javadoc) /* (non-Javadoc)

View File

@@ -29,6 +29,7 @@ import forge.game.card.CardCollectionView;
import forge.game.card.CardLists; import forge.game.card.CardLists;
import forge.game.card.CounterType; import forge.game.card.CounterType;
import forge.game.card.CardPredicates.Presets; import forge.game.card.CardPredicates.Presets;
import forge.game.card.CardZoneTable;
import forge.game.combat.Combat; import forge.game.combat.Combat;
import forge.game.combat.CombatUtil; import forge.game.combat.CombatUtil;
import forge.game.cost.Cost; import forge.game.cost.Cost;
@@ -38,9 +39,11 @@ import forge.game.player.Player;
import forge.game.player.PlayerController.BinaryChoiceType; import forge.game.player.PlayerController.BinaryChoiceType;
import forge.game.player.PlayerController.ManaPaymentPurpose; import forge.game.player.PlayerController.ManaPaymentPurpose;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.spellability.LandAbility;
import forge.game.staticability.StaticAbility; import forge.game.staticability.StaticAbility;
import forge.game.trigger.Trigger; import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerType; import forge.game.trigger.TriggerType;
import forge.game.zone.Zone;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.util.CollectionSuppliers; import forge.util.CollectionSuppliers;
import forge.util.TextUtil; import forge.util.TextUtil;
@@ -359,9 +362,11 @@ public class PhaseHandler implements java.io.Serializable {
int numDiscard = playerTurn.isUnlimitedHandSize() || handSize <= max || handSize == 0 ? 0 : handSize - max; int numDiscard = playerTurn.isUnlimitedHandSize() || handSize <= max || handSize == 0 ? 0 : handSize - max;
if (numDiscard > 0) { if (numDiscard > 0) {
final CardZoneTable table = new CardZoneTable();
for (Card c : playerTurn.getController().chooseCardsToDiscardToMaximumHandSize(numDiscard)){ for (Card c : playerTurn.getController().chooseCardsToDiscardToMaximumHandSize(numDiscard)){
playerTurn.discard(c, null); playerTurn.discard(c, null, table);
} }
table.triggerChangesZoneAll(game);
} }
// Rule 514.2 // Rule 514.2
@@ -983,7 +988,23 @@ public class PhaseHandler implements java.io.Serializable {
} }
pFirstPriority = pPlayerPriority; // all opponents have to pass before stack is allowed to resolve pFirstPriority = pPlayerPriority; // all opponents have to pass before stack is allowed to resolve
for (SpellAbility sa : chosenSa) { for (SpellAbility sa : chosenSa) {
Card saHost = sa.getHostCard();
final Zone originZone = saHost.getZone();
// TODO it has no return value if successful
pPlayerPriority.getController().playChosenSpellAbility(sa); pPlayerPriority.getController().playChosenSpellAbility(sa);
saHost = game.getCardState(saHost);
final Zone currentZone = saHost.getZone();
// Need to check if Zone did change
if (currentZone != null && originZone != null && !currentZone.equals(originZone) && (sa.isSpell() || sa instanceof LandAbility)) {
// currently there can be only one Spell put on the Stack at once, or Land Abilities be played
final CardZoneTable triggerList = new CardZoneTable();
triggerList.put(originZone.getZoneType(), currentZone.getZoneType(), saHost);
triggerList.triggerChangesZoneAll(game);
}
} }
loopCount++; loopCount++;
} while (loopCount < 999 || !pPlayerPriority.getController().isAI()); } while (loopCount < 999 || !pPlayerPriority.getController().isAI());

View File

@@ -1581,12 +1581,13 @@ public class Player extends GameEntity implements Comparable<Player> {
return numDrawnThisDrawStep; return numDrawnThisDrawStep;
} }
public final Card discard(final Card c, final SpellAbility sa) { public final Card discard(final Card c, final SpellAbility sa, CardZoneTable table) {
// TODO: This line should be moved inside CostPayment somehow // TODO: This line should be moved inside CostPayment somehow
/*if (sa != null) { /*if (sa != null) {
sa.addCostToHashList(c, "Discarded"); sa.addCostToHashList(c, "Discarded");
}*/ }*/
final Card source = sa != null ? sa.getHostCard() : null; final Card source = sa != null ? sa.getHostCard() : null;
final ZoneType origin = c.getZone().getZoneType();
boolean discardToTopOfLibrary = null != sa && sa.hasParam("DiscardToTopOfLibrary"); boolean discardToTopOfLibrary = null != sa && sa.hasParam("DiscardToTopOfLibrary");
boolean discardMadness = sa != null && sa.hasParam("Madness"); boolean discardMadness = sa != null && sa.hasParam("Madness");
@@ -1622,6 +1623,9 @@ public class Player extends GameEntity implements Comparable<Player> {
newCard = game.getAction().moveToGraveyard(c, sa, null); newCard = game.getAction().moveToGraveyard(c, sa, null);
// Play the Discard sound // Play the Discard sound
} }
if (table != null) {
table.put(origin, newCard.getZone().getZoneType(), newCard);
}
sb.append("."); sb.append(".");
numDiscardedThisTurn++; numDiscardedThisTurn++;
// Run triggers // Run triggers
@@ -1660,18 +1664,13 @@ public class Player extends GameEntity implements Comparable<Player> {
numCardsInHandStartedThisTurnWith = num; numCardsInHandStartedThisTurnWith = num;
} }
public final CardCollectionView mill(final int n) { public final CardCollectionView mill(final int n, final ZoneType destination,
return mill(n, ZoneType.Graveyard, false); final boolean bottom, SpellAbility sa, CardZoneTable table) {
}
public final CardCollectionView mill(final int n, final ZoneType zone,
final boolean bottom) {
final CardCollectionView lib = getCardsIn(ZoneType.Library); final CardCollectionView lib = getCardsIn(ZoneType.Library);
final CardCollection milled = new CardCollection(); final CardCollection milled = new CardCollection();
final int max = Math.min(n, lib.size()); final int max = Math.min(n, lib.size());
final ZoneType destination = getZone(zone).getZoneType();
for (int i = 0; i < max; i++) { for (int i = 0; i < max; i++) {
if (bottom) { if (bottom) {
milled.add(lib.get(lib.size() - i - 1)); milled.add(lib.get(lib.size() - i - 1));
@@ -1682,12 +1681,15 @@ public class Player extends GameEntity implements Comparable<Player> {
} }
CardCollectionView milledView = milled; CardCollectionView milledView = milled;
if (destination == ZoneType.Graveyard && milled.size() > 1) { if (destination == ZoneType.Graveyard && milled.size() > 1) {
milledView = GameActionUtil.orderCardsByTheirOwners(game, milled, ZoneType.Graveyard); milledView = GameActionUtil.orderCardsByTheirOwners(game, milled, ZoneType.Graveyard);
} }
for (Card m : milledView) { for (Card m : milledView) {
game.getAction().moveTo(destination, m, null, null); final ZoneType origin = m.getZone().getZoneType();
final Card d = game.getAction().moveTo(destination, m, sa, null);
table.put(origin, d.getZone().getZoneType(), d);
} }
return milled; return milled;

View File

@@ -15,44 +15,10 @@ public class TriggerChangesZoneAll extends Trigger {
@Override @Override
public boolean performTest(Map<String, Object> runParams2) { public boolean performTest(Map<String, Object> runParams2) {
@SuppressWarnings("unchecked") final CardZoneTable table = (CardZoneTable) runParams2.get("Cards");
final Map<ZoneType, CardCollection> moved = (Map<ZoneType, CardCollection>) runParams2.get("Cards");
if (hasParam("Destination")) { if (filterCards(table).isEmpty()) {
if (!getParam("Destination").equals("Any")) { return false;
if (!runParams2.get("Destination").equals(ZoneType.valueOf(getParam("Destination")))) {
return false;
}
}
}
final CardCollection allCards = new CardCollection();
if (hasParam("Origin")) {
if (!getParam("Origin").equals("Any")) {
if (getParam("Origin") == null) {
return false;
}
final List<ZoneType> origin = ZoneType.listValueOf((String)getParam("Origin"));
for (ZoneType z : origin) {
if (moved.containsKey(z)) {
allCards.addAll(moved.get(z));
}
}
}
} else {
for (CardCollection c : moved.values()) {
allCards.addAll(c);
}
}
if (hasParam("ValidCards")) {
int count = CardLists.getValidCardCount(allCards, getParam("ValidCards").split(","),this.getHostCard().getController(),
this.getHostCard());
if (count == 0) {
return false;
}
} }
return true; return true;
@@ -60,30 +26,9 @@ public class TriggerChangesZoneAll extends Trigger {
@Override @Override
public void setTriggeringObjects(SpellAbility sa) { public void setTriggeringObjects(SpellAbility sa) {
@SuppressWarnings("unchecked") final CardZoneTable table = (CardZoneTable) getRunParams().get("Cards");
final Map<ZoneType, CardCollection> moved = (Map<ZoneType, CardCollection>) getRunParams().get("Cards");
CardCollection allCards = new CardCollection(); CardCollection allCards = this.filterCards(table);
if (hasParam("Origin")) {
if (!getParam("Origin").equals("Any") && getParam("Origin") != null) {
final List<ZoneType> origin = ZoneType.listValueOf((String)getParam("Origin"));
for (ZoneType z : origin) {
if (moved.containsKey(z)) {
allCards.addAll(moved.get(z));
}
}
}
} else {
for (CardCollection c : moved.values()) {
allCards.addAll(c);
}
}
if (hasParam("ValidCards")) {
allCards = CardLists.getValidCards(allCards, getParam("ValidCards").split(","),this.getHostCard().getController(),
this.getHostCard(), sa);
}
sa.setTriggeringObject("Cards", allCards); sa.setTriggeringObject("Cards", allCards);
sa.setTriggeringObject("Amount", allCards.size()); sa.setTriggeringObject("Amount", allCards.size());
@@ -95,4 +40,50 @@ public class TriggerChangesZoneAll extends Trigger {
sb.append("Amount: ").append(sa.getTriggeringObject("Amount")); sb.append("Amount: ").append(sa.getTriggeringObject("Amount"));
return sb.toString(); return sb.toString();
} }
private CardCollection filterCards(CardZoneTable table) {
CardCollection allCards = new CardCollection();
ZoneType destination = null;
if (hasParam("Destination")) {
if (!getParam("Destination").equals("Any")) {
destination = ZoneType.valueOf(getParam("Destination"));
if (!table.containsColumn(destination)) {
return allCards;
}
}
}
if (hasParam("Origin") && !getParam("Origin").equals("Any")) {
if (getParam("Origin") == null) {
return allCards;
}
final List<ZoneType> origin = ZoneType.listValueOf(getParam("Origin"));
for (ZoneType z : origin) {
if (table.containsRow(z)) {
if (destination != null) {
allCards.addAll(table.row(z).get(destination));
} else {
for (CardCollection c : table.row(z).values()) {
allCards.addAll(c);
}
}
}
}
} else if (destination != null) {
for (CardCollection c : table.column(destination).values()) {
allCards.addAll(c);
}
} else {
for (CardCollection c : table.values()) {
allCards.addAll(c);
}
}
if (hasParam("ValidCards")) {
allCards = CardLists.getValidCards(allCards, getParam("ValidCards").split(","),
getHostCard().getController(), getHostCard(), null);
}
return allCards;
}
} }

View File

@@ -2,7 +2,7 @@ Name:Myth Unbound
ManaCost:2 G ManaCost:2 G
Types:Enchantment Types:Enchantment
S:Mode$ ReduceCost | ValidCard$ Card.IsCommander+YouOwn | Type$ Spell | Amount$ AffectedX | Description$ Your commander costs {1} less to cast for each time it's been cast from the command zone this game. S:Mode$ ReduceCost | ValidCard$ Card.IsCommander+YouOwn | Type$ Spell | Amount$ AffectedX | Description$ Your commander costs {1} less to cast for each time it's been cast from the command zone this game.
T:Mode$ ChangesZoneAll | ValidCard$ Card.IsCommander+YouOwn | Origin$ Any | Destination$ Command | Execute$ TrigDraw | TriggerZones$ Battlefield | TriggerDescription$ Whenever your commander is put into the command zone from anywhere, draw a card. T:Mode$ ChangesZone | ValidCard$ Card.IsCommander+YouOwn | Origin$ Any | Destination$ Command | Execute$ TrigDraw | TriggerZones$ Battlefield | TriggerDescription$ Whenever your commander is put into the command zone from anywhere, draw a card.
SVar:TrigDraw:DB$ Draw | Defined$ You | NumCards$ 1 SVar:TrigDraw:DB$ Draw | Defined$ You | NumCards$ 1
SVar:AffectedX:Count$CommanderCastFromCommandZone SVar:AffectedX:Count$CommanderCastFromCommandZone
AI:RemoveDeck:Random AI:RemoveDeck:Random

View File

@@ -4,8 +4,9 @@ Types:Legendary Creature Naga Shaman
PT:3/3 PT:3/3
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigMill | TriggerDescription$ Whenever CARDNAME enters the battlefield or attacks, put the top three cards of your library into your graveyard. T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigMill | TriggerDescription$ Whenever CARDNAME enters the battlefield or attacks, put the top three cards of your library into your graveyard.
T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigMill | Secondary$ True | TriggerDescription$ Whenever CARDNAME enters the battlefield or attacks, put the top three cards of your library into your graveyard. T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigMill | Secondary$ True | TriggerDescription$ Whenever CARDNAME enters the battlefield or attacks, put the top three cards of your library into your graveyard.
T:Mode$ ChangesZone | ValidCard$ Creature.YouCtrl | OncePerEffect$ True | Origin$ Library | Destination$ Graveyard | TriggerZones$ Battlefield | Execute$ TrigToken | TriggerDescription$ Whenever one or more creature cards are put into your graveyard from your library, create a 2/2 black Zombie creature token. T:Mode$ ChangesZoneAll | ValidCards$ Creature.YouOwn | Origin$ Library | Destination$ Graveyard | TriggerZones$ Battlefield | Execute$ TrigToken | TriggerDescription$ Whenever one or more creature cards are put into your graveyard from your library, create a 2/2 black Zombie creature token.
SVar:TrigMill:DB$Mill | NumCards$ 3 | Defined$ You SVar:TrigMill:DB$Mill | NumCards$ 3 | Defined$ You
SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenName$ Zombie | TokenTypes$ Creature,Zombie | TokenOwner$ You | TokenColors$ Black | TokenPower$ 2 | TokenToughness$ 2 | TokenImage$ b 2 2 zombie KTK | SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ b_2_2_zombie | TokenOwner$ You
DeckHas:Ability$Token & Ability$Graveyard
SVar:Picture:http://www.wizards.com/global/images/magic/general/sidisi_brood_tyrant.jpg SVar:Picture:http://www.wizards.com/global/images/magic/general/sidisi_brood_tyrant.jpg
Oracle:Whenever Sidisi, Brood Tyrant enters the battlefield or attacks, put the top three cards of your library into your graveyard.\nWhenever one or more creature cards are put into your graveyard from your library, create a 2/2 black Zombie creature token. Oracle:Whenever Sidisi, Brood Tyrant enters the battlefield or attacks, put the top three cards of your library into your graveyard.\nWhenever one or more creature cards are put into your graveyard from your library, create a 2/2 black Zombie creature token.

View File

@@ -5,7 +5,8 @@ PT:6/6
K:Deathtouch K:Deathtouch
K:UpkeepCost:Sac<1/Land> K:UpkeepCost:Sac<1/Land>
S:Mode$ Continuous | Affected$ You | AddKeyword$ AdjustLandPlays:1 | Description$ You may play an additional land on each of your turns. S:Mode$ Continuous | Affected$ You | AddKeyword$ AdjustLandPlays:1 | Description$ You may play an additional land on each of your turns.
T:Mode$ ChangesZone | ValidCard$ Land.YouOwn | OncePerEffect$ True | Origin$ Any | Destination$ Graveyard | TriggerZones$ Battlefield | Execute$ TrigDraw | TriggerDescription$ Whenever one or more land cards are put into your graveyard from anywhere, draw a card. T:Mode$ ChangesZoneAll | ValidCards$ Land.YouOwn | Origin$ Any | Destination$ Graveyard | TriggerZones$ Battlefield | Execute$ TrigDraw | TriggerDescription$ Whenever one or more land cards are put into your graveyard from anywhere, draw a card.
SVar:TrigDraw:DB$ Draw | Defined$ You | NumCards$ 1 SVar:TrigDraw:DB$ Draw | Defined$ You | NumCards$ 1
DeckHas:Ability$Graveyard
SVar:Picture:http://www.wizards.com/global/images/magic/general/the_gitrog_monster.jpg SVar:Picture:http://www.wizards.com/global/images/magic/general/the_gitrog_monster.jpg
Oracle:Deathtouch\nAt the beginning of your upkeep, sacrifice The Gitrog Monster unless you sacrifice a land.\nYou may play an additional land on each of your turns.\nWhenever one or more land cards are put into your graveyard from anywhere, draw a card. Oracle:Deathtouch\nAt the beginning of your upkeep, sacrifice The Gitrog Monster unless you sacrifice a land.\nYou may play an additional land on each of your turns.\nWhenever one or more land cards are put into your graveyard from anywhere, draw a card.

View File

@@ -2,8 +2,9 @@ Name:Turntimber Sower
ManaCost:2 G ManaCost:2 G
Types:Creature Elf Druid Types:Creature Elf Druid
PT:3/3 PT:3/3
T:Mode$ ChangesZone | ValidCard$ Land.YouOwn | Origin$ Any | Destination$ Graveyard | Execute$ TrigToken | OncePerEffect$ True | TriggerZones$ Battlefield | TriggerDescription$ Whenever one or more land cards are put into your graveyard from anywhere, create a 0/1 green Plant creature token. T:Mode$ ChangesZoneAll | ValidCards$ Land.YouOwn | Origin$ Any | Destination$ Graveyard | TriggerZones$ Battlefield | Execute$ TrigToken | TriggerDescription$ Whenever one or more land cards are put into your graveyard from anywhere, create a 0/1 green Plant creature token.
SVar:TrigToken:DB$Token | TokenAmount$ 1 | TokenName$ Plant | TokenTypes$ Creature,Plant | TokenOwner$ You | TokenColors$ Green | TokenPower$ 0 | TokenToughness$ 1 | TokenImage$ g 0 1 plant c18 SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ g_0_1_plant | TokenOwner$ You
AI:RemoveDeck:Random
A:AB$ ChangeZone | Cost$ G Sac<3/Creature> | TgtPrompt$ Choose target land card in your graveyard | ValidTgts$ Land.YouCtrl | Origin$ Graveyard | Destination$ Hand | SpellDescription$ Return target land card from your graveyard to your hand. A:AB$ ChangeZone | Cost$ G Sac<3/Creature> | TgtPrompt$ Choose target land card in your graveyard | ValidTgts$ Land.YouCtrl | Origin$ Graveyard | Destination$ Hand | SpellDescription$ Return target land card from your graveyard to your hand.
DeckHas:Ability$Token & Ability$Graveyard
AI:RemoveDeck:Random
Oracle:Whenever one or more land cards are put into your graveyard from anywhere, create a 0/1 green Plant creature token.\n{G}, Sacrifice three creatures: Return target land card from your graveyard to your hand. Oracle:Whenever one or more land cards are put into your graveyard from anywhere, create a 0/1 green Plant creature token.\n{G}, Sacrifice three creatures: Return target land card from your graveyard to your hand.

View File

@@ -0,0 +1,6 @@
Name:Zombie
ManaCost:no cost
Types:Creature Zombie
Colors:black
PT:2/2
Oracle:

View File

@@ -0,0 +1,6 @@
Name:Plant
ManaCost:no cost
Types:Creature Plant
Colors:green
PT:0/1
Oracle:

View File

@@ -2,7 +2,6 @@ package forge.player;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import forge.FThreads; import forge.FThreads;
import forge.card.mana.ManaCost; import forge.card.mana.ManaCost;
import forge.game.Game; import forge.game.Game;
@@ -34,7 +33,6 @@ import forge.util.gui.SGuiChoose;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -310,7 +308,7 @@ public class HumanPlay {
final HumanCostDecision hcd = new HumanCostDecision(controller, p, sourceAbility, source); final HumanCostDecision hcd = new HumanCostDecision(controller, p, sourceAbility, source);
boolean mandatory = cost.isMandatory(); boolean mandatory = cost.isMandatory();
//the following costs do not need inputs //the following costs do not need inputs
for (CostPart part : parts) { for (CostPart part : parts) {
boolean mayRemovePart = true; boolean mayRemovePart = true;
@@ -355,7 +353,7 @@ public class HumanPlay {
} }
else if (part instanceof CostGainLife) { else if (part instanceof CostGainLife) {
PaymentDecision pd = part.accept(hcd); PaymentDecision pd = part.accept(hcd);
if (pd == null) if (pd == null)
return false; return false;
else else
@@ -369,7 +367,7 @@ public class HumanPlay {
return false; return false;
} }
PaymentDecision pd = part.accept(hcd); PaymentDecision pd = part.accept(hcd);
if (pd == null) if (pd == null)
return false; return false;
else else
@@ -520,22 +518,19 @@ public class HumanPlay {
} }
} }
else if (part instanceof CostExile) { else if (part instanceof CostExile) {
final CardCollection exiledList = new CardCollection(); CostExile costExile = (CostExile) part;
ZoneType from = ZoneType.Graveyard; ZoneType from = ZoneType.Graveyard;
if ("All".equals(part.getType())) { if ("All".equals(part.getType())) {
if (!p.getController().confirmPayment(part, "Do you want to exile all cards in your graveyard?", sourceAbility)) { if (!p.getController().confirmPayment(part, "Do you want to exile all cards in your graveyard?", sourceAbility)) {
return false; return false;
} }
CardCollection cards = new CardCollection(p.getCardsIn(ZoneType.Graveyard)); costExile.executePayment(sourceAbility, p.getCardsIn(ZoneType.Graveyard));
for (final Card card : cards) {
exiledList.add(p.getGame().getAction().exile(card, null));
}
} }
else { else {
CostExile costExile = (CostExile) part;
from = costExile.getFrom(); from = costExile.getFrom();
List<Card> list = CardLists.getValidCards(p.getCardsIn(from), part.getType().split(";"), p, source, sourceAbility); CardCollection list = CardLists.getValidCards(p.getCardsIn(from), part.getType().split(";"), p, source, sourceAbility);
final int nNeeded = getAmountFromPart(costPart, source, sourceAbility); final int nNeeded = getAmountFromPart(costPart, source, sourceAbility);
if (list.size() < nNeeded) { if (list.size() < nNeeded) {
return false; return false;
@@ -546,11 +541,10 @@ public class HumanPlay {
return false; return false;
} }
list = list.subList(0, nNeeded); list = list.subList(0, nNeeded);
for (Card c : list) { costExile.executePayment(sourceAbility, list);
exiledList.add(p.getGame().getAction().exile(c, null));
}
} else { } else {
// replace this with input // replace this with input
CardCollection newList = new CardCollection();
for (int i = 0; i < nNeeded; i++) { for (int i = 0; i < nNeeded; i++) {
final Card c = p.getGame().getCard(SGuiChoose.oneOrNone("Exile from " + from, CardView.getCollection(list))); final Card c = p.getGame().getCard(SGuiChoose.oneOrNone("Exile from " + from, CardView.getCollection(list)));
if (c == null) { if (c == null) {
@@ -558,19 +552,11 @@ public class HumanPlay {
} }
list.remove(c); list.remove(c);
exiledList.add(p.getGame().getAction().exile(c, null)); newList.add(c);
} }
costExile.executePayment(sourceAbility, newList);
} }
} }
if (!exiledList.isEmpty()) {
final HashMap<String, Object> runParams = new HashMap<String, Object>();
final Map<ZoneType, CardCollection> triggerList = Maps.newEnumMap(ZoneType.class);
triggerList.put(from, exiledList);
runParams.put("Cards", triggerList);
runParams.put("Destination", ZoneType.Exile);
p.getGame().getTriggerHandler().runTrigger(TriggerType.ChangesZoneAll, runParams, false);
}
} }
else if (part instanceof CostPutCardToLib) { else if (part instanceof CostPutCardToLib) {
int amount = Integer.parseInt(((CostPutCardToLib) part).getAmount()); int amount = Integer.parseInt(((CostPutCardToLib) part).getAmount());
@@ -651,10 +637,7 @@ public class HumanPlay {
return false; return false;
} }
CardCollection cards = new CardCollection(p.getCardsIn(ZoneType.Hand)); ((CostDiscard)part).executePayment(sourceAbility, p.getCardsIn(ZoneType.Hand));
for (final Card card : cards) {
p.discard(card, sourceAbility);
}
} else { } else {
CardCollectionView list = CardLists.getValidCards(p.getCardsIn(ZoneType.Hand), part.getType(), p, source); CardCollectionView list = CardLists.getValidCards(p.getCardsIn(ZoneType.Hand), part.getType(), p, source);
int amount = getAmountFromPartX(part, source, sourceAbility); int amount = getAmountFromPartX(part, source, sourceAbility);
@@ -722,7 +705,7 @@ public class HumanPlay {
String promptCurrent = current == null ? "" : "Current Card: " + current; String promptCurrent = current == null ? "" : "Current Card: " + current;
prompt = source + "\n" + promptCurrent; prompt = source + "\n" + promptCurrent;
} }
sourceAbility.clearManaPaid(); sourceAbility.clearManaPaid();
boolean paid = p.getController().payManaCost(cost.getCostMana(), sourceAbility, prompt, false); boolean paid = p.getController().payManaCost(cost.getCostMana(), sourceAbility, prompt, false);
if (!paid) { if (!paid) {
@@ -743,42 +726,33 @@ public class HumanPlay {
return false; return false;
} }
for (Card c : inp.getSelected()) { cpl.executePayment(sourceAbility, new CardCollection(inp.getSelected()));
cpl.executePayment(sourceAbility, c);
}
if (sourceAbility != null) { if (sourceAbility != null) {
cpl.reportPaidCardsTo(sourceAbility); cpl.reportPaidCardsTo(sourceAbility);
} }
return true; return true;
} }
private static boolean handleOfferingConvokeAndDelve(final SpellAbility ability, CardCollection cardsToDelve, boolean manaInputCancelled) { private static boolean handleOfferingConvokeAndDelve(final SpellAbility ability, CardCollection cardsToDelve, boolean manaInputCancelled) {
Card hostCard = ability.getHostCard();
final Game game = hostCard.getGame();
final CardZoneTable table = new CardZoneTable();
if (!manaInputCancelled && !cardsToDelve.isEmpty()) { if (!manaInputCancelled && !cardsToDelve.isEmpty()) {
Card hostCard = ability.getHostCard();
final Game game = hostCard.getGame();
final CardCollection delved = new CardCollection();
final Map<ZoneType, CardCollection> triggerList = Maps.newEnumMap(ZoneType.class);
for (final Card c : cardsToDelve) { for (final Card c : cardsToDelve) {
hostCard.addDelved(c); hostCard.addDelved(c);
delved.add(game.getAction().exile(c, null)); final ZoneType o = c.getZone().getZoneType();
} final Card d = game.getAction().exile(c, null);
table.put(o, d.getZone().getZoneType(), d);
if (!delved.isEmpty()) {
triggerList.put(ZoneType.Graveyard, delved);
final Map<String, Object> runParams = Maps.newHashMap();
runParams.put("Cards", triggerList);
runParams.put("Destination", ZoneType.Exile);
game.getTriggerHandler().runTrigger(TriggerType.ChangesZoneAll, runParams, false);
} }
} }
if (ability.isOffering() && ability.getSacrificedAsOffering() != null) { if (ability.isOffering() && ability.getSacrificedAsOffering() != null) {
final Card offering = ability.getSacrificedAsOffering(); final Card offering = ability.getSacrificedAsOffering();
offering.setUsedToPay(false); offering.setUsedToPay(false);
if (!manaInputCancelled) { if (!manaInputCancelled) {
ability.getHostCard().getGame().getAction().sacrifice(offering, ability); game.getAction().sacrifice(offering, ability, table);
} }
ability.resetSacrificedAsOffering(); ability.resetSacrificedAsOffering();
} }
@@ -786,7 +760,7 @@ public class HumanPlay {
final Card emerge = ability.getSacrificedAsEmerge(); final Card emerge = ability.getSacrificedAsEmerge();
emerge.setUsedToPay(false); emerge.setUsedToPay(false);
if (!manaInputCancelled) { if (!manaInputCancelled) {
ability.getHostCard().getGame().getAction().sacrifice(emerge, ability); game.getAction().sacrifice(emerge, ability, table);
} }
ability.resetSacrificedAsEmerge(); ability.resetSacrificedAsEmerge();
} }
@@ -799,9 +773,12 @@ public class HumanPlay {
} }
ability.clearTappedForConvoke(); ability.clearTappedForConvoke();
} }
if (!table.isEmpty() && !manaInputCancelled) {
table.triggerChangesZoneAll(game);
}
return !manaInputCancelled; return !manaInputCancelled;
} }
public static boolean payManaCost(final PlayerControllerHuman controller, final ManaCost realCost, final CostPartMana mc, final SpellAbility ability, final Player activator, String prompt, ManaConversionMatrix matrix, boolean isActivatedSa) { public static boolean payManaCost(final PlayerControllerHuman controller, final ManaCost realCost, final CostPartMana mc, final SpellAbility ability, final Player activator, String prompt, ManaConversionMatrix matrix, boolean isActivatedSa) {
final Card source = ability.getHostCard(); final Card source = ability.getHostCard();
ManaCostBeingPaid toPay = new ManaCostBeingPaid(realCost, mc.getRestiction()); ManaCostBeingPaid toPay = new ManaCostBeingPaid(realCost, mc.getRestiction());
@@ -887,7 +864,7 @@ public class HumanPlay {
if (ability.getSacrificedAsOffering() != null) { if (ability.getSacrificedAsOffering() != null) {
System.out.println("Finishing up Offering"); System.out.println("Finishing up Offering");
offering.setUsedToPay(false); offering.setUsedToPay(false);
activator.getGame().getAction().sacrifice(offering, ability); activator.getGame().getAction().sacrifice(offering, ability, null);
ability.resetSacrificedAsOffering(); ability.resetSacrificedAsOffering();
} }
} }
@@ -898,7 +875,7 @@ public class HumanPlay {
if (ability.getSacrificedAsEmerge() != null) { if (ability.getSacrificedAsEmerge() != null) {
System.out.println("Finishing up Emerge"); System.out.println("Finishing up Emerge");
emerge.setUsedToPay(false); emerge.setUsedToPay(false);
activator.getGame().getAction().sacrifice(emerge, ability); activator.getGame().getAction().sacrifice(emerge, ability, null);
ability.resetSacrificedAsEmerge(); ability.resetSacrificedAsEmerge();
} }
} }