From 986c4a8d5cc894bea6d9e8105dd6dd514b2a9cee Mon Sep 17 00:00:00 2001 From: Hanmac Date: Mon, 24 Dec 2018 12:11:00 +0100 Subject: [PATCH 1/2] Fix Myth Unbound --- forge-game/src/main/java/forge/game/card/CardUtil.java | 1 + forge-gui/res/cardsfolder/m/myth_unbound.txt | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/forge-game/src/main/java/forge/game/card/CardUtil.java b/forge-game/src/main/java/forge/game/card/CardUtil.java index d63834e9ffe..d8f143470a6 100644 --- a/forge-game/src/main/java/forge/game/card/CardUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardUtil.java @@ -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) diff --git a/forge-gui/res/cardsfolder/m/myth_unbound.txt b/forge-gui/res/cardsfolder/m/myth_unbound.txt index c206ac6e92a..7b8021880a2 100644 --- a/forge-gui/res/cardsfolder/m/myth_unbound.txt +++ b/forge-gui/res/cardsfolder/m/myth_unbound.txt @@ -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 From af38b9b0e4a7ecdad8d33f7b9cc43595e7017636 Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Wed, 12 Dec 2018 10:02:04 +0000 Subject: [PATCH 2/2] Update Trigger ChangesZoneAll CardZoneTable: for Origin + Destination => CardCollection Tables GameAction: Refactor HumanPlay: use executePayment List if able --- .../main/java/forge/ai/ComputerUtilMana.java | 4 +- .../java/forge/ai/ability/MustBlockAi.java | 3 +- .../src/main/java/forge/game/GameAction.java | 70 +++++------ .../effects/ActivateAbilityEffect.java | 2 +- .../game/ability/effects/BalanceEffect.java | 7 +- .../ability/effects/ChangeZoneAllEffect.java | 14 +-- .../ability/effects/ChangeZoneEffect.java | 51 ++------ .../ability/effects/ControlGainEffect.java | 7 +- .../game/ability/effects/CounterEffect.java | 2 +- .../ability/effects/DestroyAllEffect.java | 27 ++--- .../game/ability/effects/DestroyEffect.java | 21 ++-- .../forge/game/ability/effects/DigEffect.java | 21 +++- .../game/ability/effects/DigUntilEffect.java | 32 +++-- .../game/ability/effects/DiscardEffect.java | 38 +++--- .../game/ability/effects/MillEffect.java | 26 ++-- .../ability/effects/SacrificeAllEffect.java | 4 +- .../game/ability/effects/SacrificeEffect.java | 17 +-- .../java/forge/game/card/CardZoneTable.java | 48 ++++++++ .../java/forge/game/cost/CostAdjustment.java | 21 ++-- .../java/forge/game/cost/CostDiscard.java | 2 +- .../forge/game/cost/CostPartWithList.java | 28 ++++- .../java/forge/game/cost/CostSacrifice.java | 3 +- .../java/forge/game/phase/PhaseHandler.java | 23 +++- .../main/java/forge/game/player/Player.java | 20 ++-- .../game/trigger/TriggerChangesZoneAll.java | 111 ++++++++---------- .../res/cardsfolder/s/sidisi_brood_tyrant.txt | 7 +- .../res/cardsfolder/t/the_gitrog_monster.txt | 5 +- .../res/cardsfolder/t/turntimber_sower.txt | 7 +- forge-gui/res/tokenscripts/b_2_2_zombie.txt | 6 + forge-gui/res/tokenscripts/g_0_1_plant.txt | 6 + .../src/main/java/forge/player/HumanPlay.java | 85 +++++--------- 31 files changed, 384 insertions(+), 334 deletions(-) create mode 100644 forge-game/src/main/java/forge/game/card/CardZoneTable.java create mode 100644 forge-gui/res/tokenscripts/b_2_2_zombie.txt create mode 100644 forge-gui/res/tokenscripts/g_0_1_plant.txt diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java b/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java index 7ad1d8a9a3e..a672d60af0f 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java @@ -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(); } diff --git a/forge-ai/src/main/java/forge/ai/ability/MustBlockAi.java b/forge-ai/src/main/java/forge/ai/ability/MustBlockAi.java index 98021672d3f..e01d0b38943 100644 --- a/forge-ai/src/main/java/forge/ai/ability/MustBlockAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/MustBlockAi.java @@ -126,7 +126,8 @@ public class MustBlockAi extends SpellAbilityAi { return chance; } - private List determineGoodBlockers(final Card attacker,final Player ai,final Player defender, SpellAbility sa, final boolean onlyLethal, final boolean testTapped) { + private List 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(); diff --git a/forge-game/src/main/java/forge/game/GameAction.java b/forge-game/src/main/java/forge/game/GameAction.java index 8c5b3454343..6a7fbd7e4e2 100644 --- a/forge-game/src/main/java/forge/game/GameAction.java +++ b/forge-game/src/main/java/forge/game/GameAction.java @@ -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() { - @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 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 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 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; } diff --git a/forge-game/src/main/java/forge/game/ability/effects/ActivateAbilityEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ActivateAbilityEffect.java index 3114cf48501..2811591354c 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ActivateAbilityEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ActivateAbilityEffect.java @@ -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.of()); p.getController().playChosenSpellAbility(manaAb); } } diff --git a/forge-game/src/main/java/forge/game/ability/effects/BalanceEffect.java b/forge-game/src/main/java/forge/game/ability/effects/BalanceEffect.java index 6e120a29453..27821b41b89 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/BalanceEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/BalanceEffect.java @@ -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); } } diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneAllEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneAllEffect.java index 4ed23fa7392..dac590fefe5 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneAllEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneAllEffect.java @@ -141,7 +141,7 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect { } // movedCards should have same timestamp long ts = game.getNextTimestamp(); - final Map 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 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 diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java index 3100c48cb32..b0ef097496b 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java @@ -429,7 +429,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect { final boolean optional = sa.hasParam("Optional"); final long ts = game.getNextTimestamp(); - final Map 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 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 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 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); - } } diff --git a/forge-game/src/main/java/forge/game/ability/effects/ControlGainEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ControlGainEffect.java index 83ed95b96b8..d0d5836f06f 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ControlGainEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ControlGainEffect.java @@ -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(); diff --git a/forge-game/src/main/java/forge/game/ability/effects/CounterEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CounterEffect.java index 9970a9001e7..c72323f4776 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/CounterEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/CounterEffect.java @@ -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")) { diff --git a/forge-game/src/main/java/forge/game/ability/effects/DestroyAllEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DestroyAllEffect.java index b1153d558fb..258654c4f77 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/DestroyAllEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/DestroyAllEffect.java @@ -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() { - @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); } } diff --git a/forge-game/src/main/java/forge/game/ability/effects/DestroyEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DestroyEffect.java index e6ac2829e45..b8d71057e54 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/DestroyEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/DestroyEffect.java @@ -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); } } diff --git a/forge-game/src/main/java/forge/game/ability/effects/DigEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DigEffect.java index efa91cea1d2..9d3fd658b19 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/DigEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/DigEffect.java @@ -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 diff --git a/forge-game/src/main/java/forge/game/ability/effects/DigUntilEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DigUntilEffect.java index 46935002a90..57d5303a45e 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/DigUntilEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/DigUntilEffect.java @@ -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 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 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 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 } diff --git a/forge-game/src/main/java/forge/game/ability/effects/DiscardEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DiscardEffect.java index b2ebcb304bc..69faad97074 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/DiscardEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/DiscardEffect.java @@ -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 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 discarded = new ArrayList(); final List 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() } diff --git a/forge-game/src/main/java/forge/game/ability/effects/MillEffect.java b/forge-game/src/main/java/forge/game/ability/effects/MillEffect.java index 68676c4fe77..51de7cf354b 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/MillEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/MillEffect.java @@ -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 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)) { diff --git a/forge-game/src/main/java/forge/game/ability/effects/SacrificeAllEffect.java b/forge-game/src/main/java/forge/game/ability/effects/SacrificeAllEffect.java index 98b07da16d6..7dd12a4bfbc 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/SacrificeAllEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/SacrificeAllEffect.java @@ -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); } } diff --git a/forge-game/src/main/java/forge/game/ability/effects/SacrificeEffect.java b/forge-game/src/main/java/forge/game/ability/effects/SacrificeEffect.java index 7b371dc58c4..d45e8617977 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/SacrificeEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/SacrificeEffect.java @@ -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 diff --git a/forge-game/src/main/java/forge/game/card/CardZoneTable.java b/forge-game/src/main/java/forge/game/card/CardZoneTable.java new file mode 100644 index 00000000000..fbec5fa601e --- /dev/null +++ b/forge-game/src/main/java/forge/game/card/CardZoneTable.java @@ -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 { + // TODO use EnumBasedTable if exist + private Table 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 delegate() { + return dataMap; + } + + public void triggerChangesZoneAll(final Game game) { + if (!isEmpty()) { + final Map runParams = Maps.newHashMap(); + runParams.put("Cards", this); + game.getTriggerHandler().runTrigger(TriggerType.ChangesZoneAll, runParams, false); + } + } +} diff --git a/forge-game/src/main/java/forge/game/cost/CostAdjustment.java b/forge-game/src/main/java/forge/game/cost/CostAdjustment.java index a76e47054e7..763af4cf279 100644 --- a/forge-game/src/main/java/forge/game/cost/CostAdjustment.java +++ b/forge-game/src/main/java/forge/game/cost/CostAdjustment.java @@ -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 triggerList = Maps.newEnumMap(ZoneType.class); - triggerList.put(ZoneType.Graveyard, delved); - final Map 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); diff --git a/forge-game/src/main/java/forge/game/cost/CostDiscard.java b/forge-game/src/main/java/forge/game/cost/CostDiscard.java index 5bf55a720ab..d12fd106041 100644 --- a/forge-game/src/main/java/forge/game/cost/CostDiscard.java +++ b/forge-game/src/main/java/forge/game/cost/CostDiscard.java @@ -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) diff --git a/forge-game/src/main/java/forge/game/cost/CostPartWithList.java b/forge-game/src/main/java/forge/game/cost/CostPartWithList.java index cb7ac9cb074..787afda62dd 100644 --- a/forge-game/src/main/java/forge/game/cost/CostPartWithList.java +++ b/forge-game/src/main/java/forge/game/cost/CostPartWithList.java @@ -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()); + } + } diff --git a/forge-game/src/main/java/forge/game/cost/CostSacrifice.java b/forge-game/src/main/java/forge/game/cost/CostSacrifice.java index e5cd9c1e9e3..f7e57a6a2a6 100644 --- a/forge-game/src/main/java/forge/game/cost/CostSacrifice.java +++ b/forge-game/src/main/java/forge/game/cost/CostSacrifice.java @@ -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) diff --git a/forge-game/src/main/java/forge/game/phase/PhaseHandler.java b/forge-game/src/main/java/forge/game/phase/PhaseHandler.java index 74738a143e0..13995f9f4eb 100644 --- a/forge-game/src/main/java/forge/game/phase/PhaseHandler.java +++ b/forge-game/src/main/java/forge/game/phase/PhaseHandler.java @@ -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()); diff --git a/forge-game/src/main/java/forge/game/player/Player.java b/forge-game/src/main/java/forge/game/player/Player.java index bd00fca5986..cf3843b7b20 100644 --- a/forge-game/src/main/java/forge/game/player/Player.java +++ b/forge-game/src/main/java/forge/game/player/Player.java @@ -1581,12 +1581,13 @@ public class Player extends GameEntity implements Comparable { 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 { 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 { 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 { } 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; diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerChangesZoneAll.java b/forge-game/src/main/java/forge/game/trigger/TriggerChangesZoneAll.java index 528b6172210..3651c3d9ae7 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerChangesZoneAll.java +++ b/forge-game/src/main/java/forge/game/trigger/TriggerChangesZoneAll.java @@ -15,44 +15,10 @@ public class TriggerChangesZoneAll extends Trigger { @Override public boolean performTest(Map runParams2) { - @SuppressWarnings("unchecked") - final Map moved = (Map) 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 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 moved = (Map) 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 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 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; + } } diff --git a/forge-gui/res/cardsfolder/s/sidisi_brood_tyrant.txt b/forge-gui/res/cardsfolder/s/sidisi_brood_tyrant.txt index a0545dd8a14..b5f12180c15 100644 --- a/forge-gui/res/cardsfolder/s/sidisi_brood_tyrant.txt +++ b/forge-gui/res/cardsfolder/s/sidisi_brood_tyrant.txt @@ -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. \ No newline at end of file +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. diff --git a/forge-gui/res/cardsfolder/t/the_gitrog_monster.txt b/forge-gui/res/cardsfolder/t/the_gitrog_monster.txt index c8816bee9c9..39c6c894a3e 100644 --- a/forge-gui/res/cardsfolder/t/the_gitrog_monster.txt +++ b/forge-gui/res/cardsfolder/t/the_gitrog_monster.txt @@ -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. diff --git a/forge-gui/res/cardsfolder/t/turntimber_sower.txt b/forge-gui/res/cardsfolder/t/turntimber_sower.txt index 7a8cbabbf9d..522ef4a93c4 100644 --- a/forge-gui/res/cardsfolder/t/turntimber_sower.txt +++ b/forge-gui/res/cardsfolder/t/turntimber_sower.txt @@ -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. diff --git a/forge-gui/res/tokenscripts/b_2_2_zombie.txt b/forge-gui/res/tokenscripts/b_2_2_zombie.txt new file mode 100644 index 00000000000..dc060486fbe --- /dev/null +++ b/forge-gui/res/tokenscripts/b_2_2_zombie.txt @@ -0,0 +1,6 @@ +Name:Zombie +ManaCost:no cost +Types:Creature Zombie +Colors:black +PT:2/2 +Oracle: \ No newline at end of file diff --git a/forge-gui/res/tokenscripts/g_0_1_plant.txt b/forge-gui/res/tokenscripts/g_0_1_plant.txt new file mode 100644 index 00000000000..76c1b8f7958 --- /dev/null +++ b/forge-gui/res/tokenscripts/g_0_1_plant.txt @@ -0,0 +1,6 @@ +Name:Plant +ManaCost:no cost +Types:Creature Plant +Colors:green +PT:0/1 +Oracle: diff --git a/forge-gui/src/main/java/forge/player/HumanPlay.java b/forge-gui/src/main/java/forge/player/HumanPlay.java index cd5ab73816e..3f634a2e499 100644 --- a/forge-gui/src/main/java/forge/player/HumanPlay.java +++ b/forge-gui/src/main/java/forge/player/HumanPlay.java @@ -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 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 runParams = new HashMap(); - final Map 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 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 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(); } }