diff --git a/forge-ai/src/main/java/forge/ai/AiController.java b/forge-ai/src/main/java/forge/ai/AiController.java index 4a2eb3651b9..077b151457f 100644 --- a/forge-ai/src/main/java/forge/ai/AiController.java +++ b/forge-ai/src/main/java/forge/ai/AiController.java @@ -1145,15 +1145,14 @@ public class AiController { } CardCollection landsWannaPlay = ComputerUtilAbility.getAvailableLandsToPlay(game, player); - CardCollection playBeforeLand = CardLists.filter(player.getCardsIn(ZoneType.Hand), new Predicate() { - @Override - public boolean apply(Card card) { - return "true".equalsIgnoreCase(card.getSVar("PlayBeforeLandDrop")); - } - }); + CardCollection playBeforeLand = CardLists.filter( + player.getCardsIn(ZoneType.Hand), CardPredicates.hasSVar("PlayBeforeLandDrop") + ); if (!playBeforeLand.isEmpty()) { - SpellAbility wantToPlayBeforeLand = chooseSpellAbilityToPlayFromList(ComputerUtilAbility.getSpellAbilities(playBeforeLand, player), false); + SpellAbility wantToPlayBeforeLand = chooseSpellAbilityToPlayFromList( + ComputerUtilAbility.getSpellAbilities(playBeforeLand, player), false + ); if (wantToPlayBeforeLand != null) { return singleSpellAbilityList(wantToPlayBeforeLand); } @@ -1163,14 +1162,28 @@ public class AiController { landsWannaPlay = filterLandsToPlay(landsWannaPlay); Log.debug("Computer " + game.getPhaseHandler().getPhase().nameForUi); if (landsWannaPlay != null && !landsWannaPlay.isEmpty() && player.canPlayLand(null)) { + // TODO search for other land it might want to play? Card land = chooseBestLandToPlay(landsWannaPlay); if (ComputerUtil.getDamageFromETB(player, land) < player.getLife() || !player.canLoseLife() || player.cantLoseForZeroOrLessLife() ) { if (!game.getPhaseHandler().is(PhaseType.MAIN1) || !isSafeToHoldLandDropForMain2(land)) { - game.PLAY_LAND_SURROGATE.setHostCard(land); final List abilities = Lists.newArrayList(); - abilities.add(game.PLAY_LAND_SURROGATE); - return abilities; + + LandAbility la = new LandAbility(land, player, null); + if (la.canPlay()) { + abilities.add(la); + } + + // add mayPlay option + for (CardPlayOption o : land.mayPlay(player)) { + la = new LandAbility(land, player, o.getAbility()); + if (la.canPlay()) { + abilities.add(la); + } + } + if (!abilities.isEmpty()) { + return abilities; + } } } } diff --git a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java index 6772b3b7905..8fa66175905 100644 --- a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java +++ b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java @@ -448,8 +448,10 @@ public class PlayerControllerAi extends PlayerController { @Override public void playChosenSpellAbility(SpellAbility sa) { // System.out.println("Playing sa: " + sa); - if (sa == sa.getHostCard().getGame().PLAY_LAND_SURROGATE) { - player.playLand(sa.getHostCard(), false); + if (sa instanceof LandAbility) { + if (sa.canPlay()) { + sa.resolve(); + } } else { ComputerUtil.handlePlayingSpellAbility(player, sa, game); } diff --git a/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityPicker.java b/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityPicker.java index c18e5ecca1b..d9f9db03937 100644 --- a/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityPicker.java +++ b/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityPicker.java @@ -11,11 +11,10 @@ import forge.game.Game; import forge.game.ability.ApiType; import forge.game.ability.effects.CharmEffect; import forge.game.card.*; -import forge.game.cost.Cost; import forge.game.phase.PhaseType; import forge.game.player.Player; -import forge.game.spellability.Ability; import forge.game.spellability.AbilitySub; +import forge.game.spellability.LandAbility; import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbilityCondition; import forge.game.zone.ZoneType; @@ -115,18 +114,10 @@ public class SpellAbilityPicker { printPhaseInfo(); SpellAbility sa = getPlannedSpellAbility(origGameScore, candidateSAs); if (sa != null) { - return transformSA(sa); + return sa; } createNewPlan(origGameScore, candidateSAs); - return transformSA(getPlannedSpellAbility(origGameScore, candidateSAs)); - } - - private SpellAbility transformSA(SpellAbility sa) { - if (sa instanceof PlayLandAbility) { - game.PLAY_LAND_SURROGATE.setHostCard(sa.getHostCard()); - return game.PLAY_LAND_SURROGATE; - } - return sa; + return getPlannedSpellAbility(origGameScore, candidateSAs); } private Plan formulatePlanWithPhase(Score origGameScore, List candidateSAs, PhaseType phase) { @@ -456,20 +447,11 @@ public class SpellAbilityPicker { return ComputerUtil.chooseSacrificeType(player, type, ability, ability.getTargetCard(), amount); } - public static class PlayLandAbility extends Ability { + public static class PlayLandAbility extends LandAbility { public PlayLandAbility(Card land) { - super(null, (Cost) null); - setHostCard(land); - } - - @Override - public boolean canPlay() { - return true; //if this ability is added anywhere, it can be assumed that land can be played - } - @Override - public void resolve() { - throw new RuntimeException("This ability is intended to indicate \"land to play\" choice only"); + super(land); } + @Override public String toUnsuppressedString() { return "Play land " + (getHostCard() != null ? getHostCard().getName() : ""); } } diff --git a/forge-game/src/main/java/forge/game/Game.java b/forge-game/src/main/java/forge/game/Game.java index f1e3fcf12d0..a70a754823f 100644 --- a/forge-game/src/main/java/forge/game/Game.java +++ b/forge-game/src/main/java/forge/game/Game.java @@ -29,7 +29,6 @@ import forge.card.CardStateName; import forge.card.CardType.Supertype; import forge.game.card.*; import forge.game.combat.Combat; -import forge.game.cost.Cost; import forge.game.event.Event; import forge.game.event.GameEventGameOutcome; import forge.game.phase.Phase; @@ -38,7 +37,6 @@ import forge.game.phase.PhaseType; import forge.game.phase.Untap; import forge.game.player.*; import forge.game.replacement.ReplacementHandler; -import forge.game.spellability.Ability; import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbilityStackInstance; import forge.game.spellability.SpellAbilityView; @@ -170,19 +168,6 @@ public class Game { } } - public final Ability PLAY_LAND_SURROGATE = new Ability(null, (Cost) null) { - @Override - public boolean canPlay() { - return true; //if this ability is added anywhere, it can be assumed that land can be played - } - @Override - public void resolve() { - throw new RuntimeException("This ability is intended to indicate \"land to play\" choice only"); - } - @Override - public String toUnsuppressedString() { return "Play land"; } - }; - private final GameEntityCache playerCache = new GameEntityCache<>(); public Player getPlayer(PlayerView playerView) { return playerCache.get(playerView); @@ -247,8 +232,6 @@ public class Game { rules = rules0; match = match0; - spabCache.put(PLAY_LAND_SURROGATE.getId(), PLAY_LAND_SURROGATE); - int highestTeam = -1; for (RegisteredPlayer psc : players0) { // Track highest team number for auto assigning unassigned teams diff --git a/forge-game/src/main/java/forge/game/GameActionUtil.java b/forge-game/src/main/java/forge/game/GameActionUtil.java index f4743f3d384..fc87dddfa2a 100644 --- a/forge-game/src/main/java/forge/game/GameActionUtil.java +++ b/forge-game/src/main/java/forge/game/GameActionUtil.java @@ -20,6 +20,9 @@ package forge.game; import com.google.common.base.Predicates; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; +import com.google.common.collect.Sets; + +import forge.card.CardStateName; import forge.card.MagicColor; import forge.card.mana.ManaCostParser; import forge.game.ability.AbilityFactory; @@ -119,9 +122,32 @@ public final class GameActionUtil { public static final List getAlternativeCosts(final SpellAbility sa, final Player activator) { final List alternatives = Lists.newArrayList(); - final Card source = sa.getHostCard(); + Card source = sa.getHostCard(); + final Game game = source.getGame(); if (sa.isSpell()) { + boolean lkicheck = false; + if (sa.hasParam("Bestow") && !source.isBestowed() && !source.isInZone(ZoneType.Battlefield)) { + if (!source.isLKI()) { + source = CardUtil.getLKICopy(source); + } + + source.animateBestow(false); + lkicheck = true; + } else if (((Spell) sa).isCastFaceDown()) { + // need a copy of the card to turn facedown without trigger anything + if (!source.isLKI()) { + source = CardUtil.getLKICopy(source); + } + source.setState(CardStateName.FaceDown, false); + lkicheck = true; + } + + if (lkicheck) { + CardCollection preList = new CardCollection(source); + game.getAction().checkStaticAbilities(false, Sets.newHashSet(source), preList); + } + for (CardPlayOption o : source.mayPlay(activator)) { // non basic are only allowed if PayManaCost is yes if (!sa.isBasicSpell() && o.getPayManaCost() == PayManaCost.NO) { @@ -186,6 +212,11 @@ public final class GameActionUtil { newSA.setDescription(sb.toString()); alternatives.add(newSA); } + + // reset static abilities + if (lkicheck) { + game.getAction().checkStaticAbilities(false, Sets.newHashSet(), CardCollection.EMPTY); + } } if (!sa.isBasicSpell()) { diff --git a/forge-game/src/main/java/forge/game/card/Card.java b/forge-game/src/main/java/forge/game/card/Card.java index 219ed4bbf20..785cc0f4116 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -5377,9 +5377,19 @@ public class Card extends GameEntity implements Comparable { } abilities.removeAll(toRemove); - if (getState(CardStateName.Original).getType().isLand() && player.canPlayLand(this)) { - game.PLAY_LAND_SURROGATE.setHostCard(this); - abilities.add(game.PLAY_LAND_SURROGATE); + if (getState(CardStateName.Original).getType().isLand()) { + LandAbility la = new LandAbility(this, player, null); + if (la.canPlay()) { + abilities.add(la); + } + + // extra for MayPlay + for (CardPlayOption o : this.mayPlay(player)) { + la = new LandAbility(this, player, o.getAbility()); + if (la.canPlay()) { + abilities.add(la); + } + } } return abilities; 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 df9a75eab5e..36c61441f34 100644 --- a/forge-game/src/main/java/forge/game/player/Player.java +++ b/forge-game/src/main/java/forge/game/player/Player.java @@ -1644,21 +1644,7 @@ public class Player extends GameEntity implements Comparable { public final boolean playLand(final Card land, final boolean ignoreZoneAndTiming) { // Dakkon Blackblade Avatar will use a similar effect if (canPlayLand(land, ignoreZoneAndTiming)) { - land.setController(this, 0); - if (land.isFaceDown()) { - land.turnFaceUp(); - } - game.getAction().moveTo(getZone(ZoneType.Battlefield), land, null, new HashMap()); - - // play a sound - game.fireEvent(new GameEventLandPlayed(this, land)); - - // Run triggers - final Map runParams = Maps.newHashMap(); - runParams.put("Card", land); - game.getTriggerHandler().runTrigger(TriggerType.LandPlayed, runParams, false); - game.getStack().unfreezeStack(); - addLandPlayedThisTurn(); + this.playLandNoCheck(land); return true; } @@ -1666,6 +1652,24 @@ public class Player extends GameEntity implements Comparable { return false; } + public final void playLandNoCheck(final Card land) { + land.setController(this, 0); + if (land.isFaceDown()) { + land.turnFaceUp(); + } + game.getAction().moveTo(getZone(ZoneType.Battlefield), land, null, new HashMap()); + + // play a sound + game.fireEvent(new GameEventLandPlayed(this, land)); + + // Run triggers + final Map runParams = Maps.newHashMap(); + runParams.put("Card", land); + game.getTriggerHandler().runTrigger(TriggerType.LandPlayed, runParams, false); + game.getStack().unfreezeStack(); + addLandPlayedThisTurn(); + } + public final boolean canPlayLand(final Card land) { return canPlayLand(land, false); } diff --git a/forge-game/src/main/java/forge/game/spellability/LandAbility.java b/forge-game/src/main/java/forge/game/spellability/LandAbility.java new file mode 100644 index 00000000000..a524a7222a9 --- /dev/null +++ b/forge-game/src/main/java/forge/game/spellability/LandAbility.java @@ -0,0 +1,119 @@ +/* + * Forge: Play Magic: the Gathering. + * Copyright (C) 2011 Forge Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package forge.game.spellability; + +import forge.game.Game; +import forge.game.card.Card; +import forge.game.cost.Cost; +import forge.game.player.Player; +import forge.game.staticability.StaticAbility; +import forge.game.zone.Zone; +import forge.game.zone.ZoneType; + +public class LandAbility extends Ability { + + public LandAbility(Card sourceCard, Player p, StaticAbility mayPlay) { + super(sourceCard, (Cost)null); + setActivatingPlayer(p); + setMayPlay(mayPlay); + } + public LandAbility(Card sourceCard) { + this(sourceCard, sourceCard.getController(), (StaticAbility)null); + } + @Override + public boolean canPlay() { + final Card land = this.getHostCard(); + final Player p = this.getActivatingPlayer(); + final Game game = p.getGame(); + if (!p.canCastSorcery()) { + return false; + } + + // CantBeCast static abilities + for (final Card ca : game.getCardsIn(ZoneType.listValueOf("Battlefield,Command"))) { + final Iterable staticAbilities = ca.getStaticAbilities(); + for (final StaticAbility stAb : staticAbilities) { + if (stAb.applyAbility("CantPlayLand", land, this)) { + return false; + } + } + } + + if (land != null) { + final boolean mayPlay = getMayPlay() != null; + if (land.getOwner() != p && !mayPlay) { + return false; + } + + final Zone zone = game.getZoneOf(land); + if (zone != null && (zone.is(ZoneType.Battlefield) || (!zone.is(ZoneType.Hand) && !mayPlay))) { + return false; + } + } + + // **** Check for land play limit per turn **** + // Dev Mode + if (p.getController().canPlayUnlimitedLands() || p.hasKeyword("You may play any number of additional lands on each of your turns.")) { + return true; + } + + // check for adjusted max lands play per turn + int adjMax = 1; + for (String keyword : p.getKeywords()) { + if (keyword.startsWith("AdjustLandPlays")) { + final String[] k = keyword.split(":"); + adjMax += Integer.valueOf(k[1]); + } + } + if (p.getLandsPlayedThisTurn() < adjMax) { + return true; + } + return false; + } + @Override + public void resolve() { + getActivatingPlayer().playLandNoCheck(getHostCard()); + + // increase mayplay used + if (getMayPlay() != null) { + getMayPlay().incMayPlayTurn(); + } + } + @Override + public String toUnsuppressedString() { + StringBuilder sb = new StringBuilder("Play land"); + StaticAbility sta = getMayPlay(); + if (sta != null) { + Card source = sta.getHostCard(); + if (!source.equals(getHostCard())) { + sb.append(" by "); + if ((source.isEmblem() || source.getType().hasSubtype("Effect")) + && source.getEffectSource() != null) { + sb.append(source.getEffectSource()); + } else { + sb.append(source); + } + if (sta.hasParam("MayPlayText")) { + sb.append(" (").append(sta.getParam("MayPlayText")).append(")"); + } + } + } + return sb.toString(); + } + +} \ No newline at end of file diff --git a/forge-gui-desktop/src/test/java/forge/ai/simulation/SpellAbilityPickerTest.java b/forge-gui-desktop/src/test/java/forge/ai/simulation/SpellAbilityPickerTest.java index 3683b33cc7d..d7cfa66d7c7 100644 --- a/forge-gui-desktop/src/test/java/forge/ai/simulation/SpellAbilityPickerTest.java +++ b/forge-gui-desktop/src/test/java/forge/ai/simulation/SpellAbilityPickerTest.java @@ -70,7 +70,7 @@ public class SpellAbilityPickerTest extends SimulationTestCase { SpellAbilityPicker picker = new SpellAbilityPicker(game, p); SpellAbility sa = picker.chooseSpellAbilityToPlay(null); - assertEquals(game.PLAY_LAND_SURROGATE, sa); + //assertEquals(game.PLAY_LAND_SURROGATE, sa); assertEquals(mountain, sa.getHostCard()); Plan plan = picker.getPlan(); diff --git a/forge-gui/src/main/java/forge/match/input/InputPassPriority.java b/forge-gui/src/main/java/forge/match/input/InputPassPriority.java index 9078f3b671f..d793efba100 100644 --- a/forge-gui/src/main/java/forge/match/input/InputPassPriority.java +++ b/forge-gui/src/main/java/forge/match/input/InputPassPriority.java @@ -23,6 +23,7 @@ import java.util.List; import forge.game.Game; import forge.game.card.Card; import forge.game.player.Player; +import forge.game.spellability.LandAbility; import forge.game.spellability.SpellAbility; import forge.model.FModel; import forge.player.GamePlayerUtil; @@ -162,7 +163,7 @@ public class InputPassPriority extends InputSyncronizedBase { if (sa.isSpell()) { return "cast spell"; } - if (sa == card.getGame().PLAY_LAND_SURROGATE) { + if (sa instanceof LandAbility) { return "play land"; } return "activate ability"; diff --git a/forge-gui/src/main/java/forge/player/HumanPlay.java b/forge-gui/src/main/java/forge/player/HumanPlay.java index d0627961b38..9c103127970 100644 --- a/forge-gui/src/main/java/forge/player/HumanPlay.java +++ b/forge-gui/src/main/java/forge/player/HumanPlay.java @@ -6,6 +6,7 @@ import java.util.List; import java.util.Map; import forge.game.cost.*; +import forge.game.spellability.LandAbility; import forge.game.spellability.OptionalCostValue; import forge.game.spellability.Spell; import forge.util.TextUtil; @@ -66,8 +67,11 @@ public class HumanPlay { public final static boolean playSpellAbility(final PlayerControllerHuman controller, final Player p, SpellAbility sa) { FThreads.assertExecutedByEdt(false); - if (sa == controller.getGame().PLAY_LAND_SURROGATE) { - p.playLand(sa.getHostCard(), false); + if (sa instanceof LandAbility) { + sa.setActivatingPlayer(p); + if (sa.canPlay()) { + sa.resolve(); + } return false; }