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

View File

@@ -126,7 +126,8 @@ public class MustBlockAi extends SpellAbilityAi {
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 TargetRestrictions abTgt = sa.getTargetRestrictions();

View File

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

View File

@@ -51,7 +51,7 @@ public class ActivateAbilityEffect extends SpellAbilityEffect {
continue;
}
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);
}
}

View File

@@ -5,6 +5,7 @@ import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardLists;
import forge.game.card.CardZoneTable;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
@@ -41,6 +42,7 @@ public class BalanceEffect extends SpellAbilityEffect {
min = Math.min(min, validCards.get(i).size());
}
CardZoneTable table = new CardZoneTable();
for(int i = 0; i < players.size(); i++) {
Player p = players.get(i);
int numToBalance = validCards.get(i).size() - min;
@@ -50,15 +52,16 @@ public class BalanceEffect extends SpellAbilityEffect {
if (zone.equals(ZoneType.Hand)) {
for (Card card : p.getController().chooseCardsToDiscardFrom(p, sa, validCards.get(i), numToBalance, numToBalance)) {
if ( null == card ) continue;
p.discard(card, sa);
p.discard(card, sa, table);
}
} else { // Battlefield
// TODO: "can'e be sacrificed"
for(Card card : p.getController().choosePermanentsToSacrifice(sa, numToBalance, numToBalance, validCards.get(i), valid)) {
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
long ts = game.getNextTimestamp();
final Map<ZoneType, CardCollection> triggerList = Maps.newEnumMap(ZoneType.class);
final CardZoneTable triggerList = new CardZoneTable();
for (final Card c : cards) {
final Zone originZone = game.getZoneOf(c);
@@ -209,21 +209,13 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect {
}
if (!movedCard.getZone().equals(originZone)) {
if (!triggerList.containsKey(originZone.getZoneType())) {
triggerList.put(originZone.getZoneType(), new CardCollection());
}
triggerList.get(originZone.getZoneType()).add(movedCard);
triggerList.put(originZone.getZoneType(), movedCard.getZone().getZoneType(), movedCard);
}
}
game.getTriggerHandler().resetActiveTriggers(false);
if (!triggerList.isEmpty()) {
final Map<String, Object> runParams = Maps.newHashMap();
runParams.put("Cards", triggerList);
runParams.put("Destination", destination);
game.getTriggerHandler().runTrigger(TriggerType.ChangesZoneAll, runParams, false);
}
triggerList.triggerChangesZoneAll(game);
// if Shuffle parameter exists, and any amount of cards were owned by
// that player, then shuffle that library

View File

@@ -429,7 +429,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
final boolean optional = sa.hasParam("Optional");
final long ts = game.getNextTimestamp();
final Map<ZoneType, CardCollection> triggerList = Maps.newEnumMap(ZoneType.class);
final CardZoneTable triggerList = new CardZoneTable();
for (final Card tgtC : tgtCards) {
if (tgt != null && tgtC.isInPlay() && !tgtC.canBeTargetedBy(sa)) {
@@ -602,10 +602,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
}
}
if (!movedCard.getZone().equals(originZone)) {
if (!triggerList.containsKey(originZone.getZoneType())) {
triggerList.put(originZone.getZoneType(), new CardCollection());
}
triggerList.get(originZone.getZoneType()).add(movedCard);
triggerList.put(originZone.getZoneType(), movedCard.getZone().getZoneType(), movedCard);
if (remember != null) {
hostCard.addRemembered(movedCard);
@@ -619,12 +616,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
}
}
if (!triggerList.isEmpty()) {
final Map<String, Object> runParams = Maps.newHashMap();
runParams.put("Cards", triggerList);
runParams.put("Destination", destination);
game.getTriggerHandler().runTrigger(TriggerType.ChangesZoneAll, runParams, false);
}
triggerList.triggerChangesZoneAll(game);
// for things like Gaea's Blessing
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();
long ts = game.getNextTimestamp();
final Map<ZoneType, CardCollection> triggerList = Maps.newEnumMap(ZoneType.class);
for (Card c : chosenCards) {
final CardZoneTable triggerList = new CardZoneTable();
for (final Card c : chosenCards) {
Card movedCard = null;
final Zone originZone = game.getZoneOf(c);
if (destination.equals(ZoneType.Library)) {
@@ -1117,10 +1109,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
movedCards.add(movedCard);
if (originZone != null) {
if (!triggerList.containsKey(originZone.getZoneType())) {
triggerList.put(originZone.getZoneType(), new CardCollection());
}
triggerList.get(originZone.getZoneType()).add(movedCard);
triggerList.put(originZone.getZoneType(), movedCard.getZone().getZoneType(), movedCard);
}
if (champion) {
@@ -1152,13 +1141,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
player.shuffle(sa);
}
if (!triggerList.isEmpty()) {
final Map<String, Object> runParams = Maps.newHashMap();
runParams.put("Cards", triggerList);
runParams.put("Destination", destination);
game.getTriggerHandler().runTrigger(TriggerType.ChangesZoneAll, runParams, false);
}
triggerList.triggerChangesZoneAll(game);
}
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) {
@Override
public void resolve() {
if (bNoRegen) {
game.getAction().destroyNoRegeneration(c, null);
} else {
game.getAction().destroy(c, null);
}
game.getAction().destroy(c, null, !bNoRegen, null);
}
};
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
if (tgtSA.isAbility() && sa.hasParam("DestroyPermanent")) {
game.getAction().destroy(tgtSACard, sa);
game.getAction().destroy(tgtSACard, sa, true, null);
}
if (sa.hasParam("RememberCountered")) {

View File

@@ -1,6 +1,5 @@
package forge.game.ability.effects;
import com.google.common.base.Predicate;
import forge.game.Game;
import forge.game.GameActionUtil;
import forge.game.ability.AbilityUtils;
@@ -8,7 +7,9 @@ import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.card.CardUtil;
import forge.game.card.CardZoneTable;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
@@ -78,30 +79,20 @@ public class DestroyAllEffect extends SpellAbilityEffect {
}
// exclude cards that can't be destroyed at this moment
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(Card card) {
return card.canBeDestroyed();
}
});
list = CardLists.filter(list, CardPredicates.Presets.CAN_BE_DESTROYED);
if (list.size() > 1) {
list = GameActionUtil.orderCardsByTheirOwners(game, list, ZoneType.Graveyard);
}
if (noRegen) {
for (Card c : list) {
if (game.getAction().destroyNoRegeneration(c, sa) && remDestroyed) {
card.addRemembered(CardUtil.getLKICopy(c));
}
}
} else {
for (Card c : list) {
if (game.getAction().destroy(c, sa) && remDestroyed) {
card.addRemembered(CardUtil.getLKICopy(c));
}
CardZoneTable table = new CardZoneTable();
for (Card c : list) {
if (game.getAction().destroy(c, sa, !noRegen, table) && 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.CardCollection;
import forge.game.card.CardUtil;
import forge.game.card.CardZoneTable;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import java.util.Iterator;
@@ -77,8 +77,6 @@ public class DestroyEffect extends SpellAbilityEffect {
CardCollection tgtCards = getTargetCards(sa);
CardCollection untargetedCards = new CardCollection();
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (sa.hasParam("Radiance")) {
for (final Card c : CardUtil.getRadiance(card, tgtCards.get(0),
sa.getParam("ValidTgts").split(","))) {
@@ -90,19 +88,18 @@ public class DestroyEffect extends SpellAbilityEffect {
tgtCards = (CardCollection) GameActionUtil.orderCardsByTheirOwners(game, tgtCards, ZoneType.Graveyard);
}
CardZoneTable table = new CardZoneTable();
for (final Card tgtC : tgtCards) {
if (tgtC.isInPlay() && ((tgt == null) || tgtC.canBeTargetedBy(sa))) {
if (tgtC.isInPlay() && (!sa.usesTargeting() || tgtC.canBeTargetedBy(sa))) {
boolean destroyed = false;
final Card lki = CardUtil.getLKICopy(tgtC);
if (remAttached) {
card.addRemembered(tgtC.getAttachedCards());
}
if (sac) {
destroyed = game.getAction().sacrifice(tgtC, sa) != null;
} else if (noRegen) {
destroyed = game.getAction().destroyNoRegeneration(tgtC, sa);
destroyed = game.getAction().sacrifice(tgtC, sa, table) != null;
} else {
destroyed = game.getAction().destroy(tgtC, sa);
destroyed = game.getAction().destroy(tgtC, sa, !noRegen, table);
}
if (destroyed && remDestroyed) {
card.addRemembered(tgtC);
@@ -121,16 +118,16 @@ public class DestroyEffect extends SpellAbilityEffect {
if (unTgtC.isInPlay()) {
boolean destroyed = false;
if (sac) {
destroyed = game.getAction().sacrifice(unTgtC, sa) != null;
} else if (noRegen) {
destroyed = game.getAction().destroyNoRegeneration(unTgtC, sa);
destroyed = game.getAction().sacrifice(unTgtC, sa, table) != null;
} else {
destroyed = game.getAction().destroy(unTgtC, sa);
destroyed = game.getAction().destroy(unTgtC, sa, !noRegen, table);
} if (destroyed && remDestroyed) {
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.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.card.CardZoneTable;
import forge.game.card.CounterType;
import forge.game.player.DelayedReveal;
import forge.game.player.Player;
@@ -109,6 +110,7 @@ public class DigEffect extends SpellAbilityEffect {
}
}
CardZoneTable table = new CardZoneTable();
for (final Player p : tgtPlayers) {
if (tgt != null && !p.canBeTargetedBy(sa)) {
continue;
@@ -301,6 +303,7 @@ public class DigEffect extends SpellAbilityEffect {
effectHost = sa.getHostCard();
}
for (Card c : movedCards) {
final ZoneType origin = c.getZone().getZoneType();
final PlayerZone zone = c.getOwner().getZone(destZone1);
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);
}
}
if (!origin.equals(c.getZone().getZoneType())) {
table.put(origin, c.getZone().getZoneType(), c);
}
if (sa.hasParam("ExileFaceDown")) {
c.setState(CardStateName.FaceDown, true);
@@ -357,11 +363,16 @@ public class DigEffect extends SpellAbilityEffect {
Collections.reverse(afterOrder);
}
for (final Card c : afterOrder) {
final ZoneType origin = c.getZone().getZoneType();
Card m;
if (destZone2 == ZoneType.Library) {
game.getAction().moveToLibrary(c, libraryPosition2, sa);
m = game.getAction().moveToLibrary(c, libraryPosition2, sa);
}
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
for (int i = 0; i < rest.size(); i++) {
Card c = rest.get(i);
final ZoneType origin = c.getZone().getZoneType();
final PlayerZone toZone = c.getOwner().getZone(destZone2);
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()) {
for (final String kw : keywords) {
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

View File

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

View File

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

View File

@@ -1,23 +1,25 @@
package forge.game.ability.effects;
import forge.card.CardStateName;
import forge.game.Game;
import forge.game.GameLogEntryType;
import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.card.CardCollectionView;
import forge.game.card.CardZoneTable;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
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 {
@Override
public void resolve(SpellAbility sa) {
final Card source = sa.getHostCard();
final Game game = source.getGame();
final int numCards = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumCards"), sa);
final boolean bottom = sa.hasParam("FromBottom");
final boolean facedown = sa.hasParam("ExileFaceDown");
@@ -28,27 +30,27 @@ public class MillEffect extends SpellAbilityEffect {
source.clearRemembered();
}
final TargetRestrictions tgt = sa.getTargetRestrictions();
ZoneType destination = ZoneType.smartValueOf(sa.getParam("Destination"));
if (destination == null) {
destination = ZoneType.Graveyard;
}
final CardZoneTable table = new CardZoneTable();
for (final Player p : getTargetPlayers(sa)) {
if ((tgt == null) || p.canBeTargetedBy(sa)) {
if (!sa.usesTargeting() || p.canBeTargetedBy(sa)) {
if (sa.hasParam("Optional")) {
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)) {
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
// graveyard to figure out which ones were milled.
if (!facedown && reveal) { // do not reveal when exiling face down
if (showRevealDialog) {
p.getGame().getAction().reveal(milled, p, false);
game.getAction().reveal(milled, p, false);
}
StringBuilder sb = new StringBuilder();
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
@@ -85,10 +90,7 @@ public class MillEffect extends SpellAbilityEffect {
final StringBuilder sb = new StringBuilder();
final int numCards = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumCards"), sa);
final List<Player> tgtPlayers = getTargetPlayers(sa);
for (final Player p : tgtPlayers) {
sb.append(p.toString()).append(" ");
}
sb.append(Lang.joinHomogenous(getTargetPlayers(sa))).append(" ");
final ZoneType dest = ZoneType.smartValueOf(sa.getParam("Destination"));
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);
}
CardZoneTable table = new CardZoneTable();
for (Card sac : list) {
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);
}
}
table.triggerChangesZoneAll(game);
}
}

View File

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

View File

@@ -219,6 +219,7 @@ public final class CardUtil {
newCopy.setSetCode(in.getSetCode());
newCopy.setOwner(in.getOwner());
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
// (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;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import forge.card.CardStateName;
import forge.card.mana.ManaAtom;
import forge.card.mana.ManaCost;
@@ -19,14 +17,15 @@ import forge.game.spellability.Spell;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetChoices;
import forge.game.staticability.StaticAbility;
import forge.game.trigger.TriggerType;
import forge.game.zone.ZoneType;
import org.apache.commons.lang3.StringUtils;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import com.google.common.collect.Lists;
import org.apache.commons.lang3.StringUtils;
public class CostAdjustment {
public static Cost adjust(final Cost cost, final SpellAbility sa) {
@@ -214,7 +213,7 @@ public class CostAdjustment {
if (sa.getHostCard().hasKeyword(Keyword.DELVE)) {
sa.getHostCard().clearDelved();
final CardCollection delved = new CardCollection();
final CardZoneTable table = new CardZoneTable();
final Player pc = sa.getActivatingPlayer();
final CardCollection mutableGrave = new CardCollection(pc.getCardsIn(ZoneType.Graveyard));
final CardCollectionView toExile = pc.getController().chooseCardsToDelve(cost.getUnpaidShards(ManaCostShard.GENERIC), mutableGrave);
@@ -224,17 +223,11 @@ public class CostAdjustment {
cardsToDelveOut.add(c);
} else if (!test) {
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()) {
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);
}
table.triggerChangesZoneAll(game);
}
if (sa.getHostCard().hasKeyword(Keyword.CONVOKE)) {
adjustCostByConvokeOrImprovise(cost, sa, false, test);

View File

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

View File

@@ -21,8 +21,10 @@ import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardUtil;
import forge.game.card.CardZoneTable;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.Zone;
/**
* The Class CostPartWithList.
@@ -35,6 +37,8 @@ public abstract class CostPartWithList extends CostPart {
/** The lists: one for LKI, one for the actual cards. */
private final CardCollection lkiList = 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 allows to avoid duplication
@@ -52,6 +56,7 @@ public abstract class CostPartWithList extends CostPart {
public final void resetLists() {
lkiList.clear();
cardList.clear();
table.clear();
}
/**
@@ -97,13 +102,21 @@ public abstract class CostPartWithList extends CostPart {
public final boolean executePayment(SpellAbility ability, Card targetCard) {
lkiList.add(CardUtil.getLKICopy(targetCard));
final Zone origin = targetCard.getZone();
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
// (e.g. Necroskitter + a creature dying from a -1/-1 counter on a cost payment).
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;
}
@@ -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.
lkiList.addAll(targetCards);
cardList.addAll(doListPayment(ability, targetCards));
handleChangeZoneTrigger(ability);
return true;
}
for (Card c: targetCards) {
executePayment(ability, c);
}
handleChangeZoneTrigger(ability);
return true;
}
@@ -145,4 +160,15 @@ public abstract class CostPartWithList extends CostPart {
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
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)

View File

@@ -29,6 +29,7 @@ import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.card.CounterType;
import forge.game.card.CardPredicates.Presets;
import forge.game.card.CardZoneTable;
import forge.game.combat.Combat;
import forge.game.combat.CombatUtil;
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.ManaPaymentPurpose;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.LandAbility;
import forge.game.staticability.StaticAbility;
import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerType;
import forge.game.zone.Zone;
import forge.game.zone.ZoneType;
import forge.util.CollectionSuppliers;
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;
if (numDiscard > 0) {
final CardZoneTable table = new CardZoneTable();
for (Card c : playerTurn.getController().chooseCardsToDiscardToMaximumHandSize(numDiscard)){
playerTurn.discard(c, null);
playerTurn.discard(c, null, table);
}
table.triggerChangesZoneAll(game);
}
// 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
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);
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++;
} while (loopCount < 999 || !pPlayerPriority.getController().isAI());

View File

@@ -1581,12 +1581,13 @@ public class Player extends GameEntity implements Comparable<Player> {
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
/*if (sa != null) {
sa.addCostToHashList(c, "Discarded");
}*/
final Card source = sa != null ? sa.getHostCard() : null;
final ZoneType origin = c.getZone().getZoneType();
boolean discardToTopOfLibrary = null != sa && sa.hasParam("DiscardToTopOfLibrary");
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);
// Play the Discard sound
}
if (table != null) {
table.put(origin, newCard.getZone().getZoneType(), newCard);
}
sb.append(".");
numDiscardedThisTurn++;
// Run triggers
@@ -1660,18 +1664,13 @@ public class Player extends GameEntity implements Comparable<Player> {
numCardsInHandStartedThisTurnWith = num;
}
public final CardCollectionView mill(final int n) {
return mill(n, ZoneType.Graveyard, false);
}
public final CardCollectionView mill(final int n, final ZoneType zone,
final boolean bottom) {
public final CardCollectionView mill(final int n, final ZoneType destination,
final boolean bottom, SpellAbility sa, CardZoneTable table) {
final CardCollectionView lib = getCardsIn(ZoneType.Library);
final CardCollection milled = new CardCollection();
final int max = Math.min(n, lib.size());
final ZoneType destination = getZone(zone).getZoneType();
for (int i = 0; i < max; i++) {
if (bottom) {
milled.add(lib.get(lib.size() - i - 1));
@@ -1682,12 +1681,15 @@ public class Player extends GameEntity implements Comparable<Player> {
}
CardCollectionView milledView = milled;
if (destination == ZoneType.Graveyard && milled.size() > 1) {
milledView = GameActionUtil.orderCardsByTheirOwners(game, milled, ZoneType.Graveyard);
}
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;

View File

@@ -15,44 +15,10 @@ public class TriggerChangesZoneAll extends Trigger {
@Override
public boolean performTest(Map<String, Object> runParams2) {
@SuppressWarnings("unchecked")
final Map<ZoneType, CardCollection> moved = (Map<ZoneType, CardCollection>) runParams2.get("Cards");
final CardZoneTable table = (CardZoneTable) runParams2.get("Cards");
if (hasParam("Destination")) {
if (!getParam("Destination").equals("Any")) {
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;
}
if (filterCards(table).isEmpty()) {
return false;
}
return true;
@@ -60,30 +26,9 @@ public class TriggerChangesZoneAll extends Trigger {
@Override
public void setTriggeringObjects(SpellAbility sa) {
@SuppressWarnings("unchecked")
final Map<ZoneType, CardCollection> moved = (Map<ZoneType, CardCollection>) getRunParams().get("Cards");
final CardZoneTable table = (CardZoneTable) getRunParams().get("Cards");
CardCollection allCards = new CardCollection();
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);
}
CardCollection allCards = this.filterCards(table);
sa.setTriggeringObject("Cards", allCards);
sa.setTriggeringObject("Amount", allCards.size());
@@ -95,4 +40,50 @@ public class TriggerChangesZoneAll extends Trigger {
sb.append("Amount: ").append(sa.getTriggeringObject("Amount"));
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
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.
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:AffectedX:Count$CommanderCastFromCommandZone
AI:RemoveDeck:Random

View File

@@ -4,8 +4,9 @@ Types:Legendary Creature Naga Shaman
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$ 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: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
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:UpkeepCost:Sac<1/Land>
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.
SVar:TrigDraw:DB$ Draw | Defined$ You | NumCards$ 1
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
DeckHas:Ability$Graveyard
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.

View File

@@ -2,8 +2,9 @@ Name:Turntimber Sower
ManaCost:2 G
Types:Creature Elf Druid
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.
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
AI:RemoveDeck:Random
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 | TokenScript$ g_0_1_plant | TokenOwner$ You
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.

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