diff --git a/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java b/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java index f0cda34d2a2..c9913b4f111 100644 --- a/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java +++ b/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java @@ -148,6 +148,7 @@ import mage.target.common.TargetDiscard; import mage.target.common.TargetPermanentOrPlayer; import mage.target.common.TargetSpellOrPermanent; import mage.util.Copier; +import mage.util.MessageToClient; import mage.util.TreeNode; import org.apache.log4j.Logger; @@ -1323,6 +1324,11 @@ public class ComputerPlayer extends PlayerImpl implements Player { @Override public boolean chooseUse(Outcome outcome, String message, Ability source, Game game) { + return this.chooseUse(outcome, new MessageToClient(message), source, game); + } + + @Override + public boolean chooseUse(Outcome outcome, MessageToClient message, Ability source, Game game) { log.debug("chooseUse: " + outcome.isGood()); // Be proactive! Always use abilities, the evaluation function will decide if it's good or not // Otherwise some abilities won't be used by AI like LoseTargetEffect that has "bad" outcome diff --git a/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java b/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java index 4246c916522..b628991865e 100644 --- a/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java +++ b/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java @@ -168,12 +168,17 @@ public class HumanPlayer extends PlayerImpl { @Override public boolean chooseUse(Outcome outcome, String message, Ability source, Game game) { + return this.chooseUse(outcome, new MessageToClient(message), source, game); + } + + @Override + public boolean chooseUse(Outcome outcome, MessageToClient message, Ability source, Game game) { if (source != null) { - Boolean answer = requestAutoAnswerId.get(source.getOriginalId() + "#" + message); + Boolean answer = requestAutoAnswerId.get(source.getOriginalId() + "#" + message.getMessage()); if (answer != null) { return answer; } else { - answer = requestAutoAnswerText.get(message); + answer = requestAutoAnswerText.get(message.getMessage()); if (answer != null) { return answer; } @@ -181,7 +186,10 @@ public class HumanPlayer extends PlayerImpl { } updateGameStatePriority("chooseUse", game); do { - game.fireAskPlayerEvent(playerId, new MessageToClient(message, getRelatedObjectName(source, game)), source); + if (message.getSecondMessage() == null) { + message.setSecondMessage(getRelatedObjectName(source, game)); + } + game.fireAskPlayerEvent(playerId, message, source); waitForResponse(game); } while (response.getBoolean() == null && !abort); if (!abort) { diff --git a/Mage.Sets/src/mage/sets/khansoftarkir/VillainousWealth.java b/Mage.Sets/src/mage/sets/khansoftarkir/VillainousWealth.java index 4780c7e18a2..cf5fa4b68a7 100644 --- a/Mage.Sets/src/mage/sets/khansoftarkir/VillainousWealth.java +++ b/Mage.Sets/src/mage/sets/khansoftarkir/VillainousWealth.java @@ -108,6 +108,7 @@ class VillainousWealthEffect extends OneShotEffect { OuterLoop: while (cardsToExile.count(filter, game) > 0) { TargetCardInExile target = new TargetCardInExile(0, 1, filter, exileId, false); + target.setNotTarget(true); while (cardsToExile.count(filter, game) > 0 && controller.choose(Outcome.PlayForFree, cardsToExile, target, game)) { Card card = game.getCard(target.getFirstTarget()); if (card != null) { diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/oneshot/destroy/BoilTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/oneshot/destroy/BoilTest.java new file mode 100644 index 00000000000..c7eabd0ac60 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/oneshot/destroy/BoilTest.java @@ -0,0 +1,66 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package org.mage.test.cards.abilities.oneshot.destroy; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author LevelX2 + */ +public class BoilTest extends CardTestPlayerBase { + + @Test + public void testDestroy() { + addCard(Zone.BATTLEFIELD, playerA, "Island", 1); + addCard(Zone.BATTLEFIELD, playerA, "Breeding Pool"); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + // Destroy all Islands. + addCard(Zone.HAND, playerA, "Boil"); // {3}{R} + + addCard(Zone.BATTLEFIELD, playerB, "Island", 1); + addCard(Zone.BATTLEFIELD, playerB, "Breeding Pool"); + addCard(Zone.BATTLEFIELD, playerB, "Mountain", 2); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Boil"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, "Island", 0); + assertPermanentCount(playerA, "Breeding Pool", 0); + assertPermanentCount(playerA, "Mountain", 2); + + assertPermanentCount(playerB, "Island", 0); + assertPermanentCount(playerB, "Breeding Pool", 0); + assertPermanentCount(playerB, "Mountain", 2); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java index eb098ea1829..c815c8f94c4 100644 --- a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java +++ b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java @@ -97,6 +97,7 @@ import mage.target.common.TargetCardInHand; import mage.target.common.TargetCardInLibrary; import mage.target.common.TargetCreaturePermanentAmount; import mage.target.common.TargetPermanentOrPlayer; +import mage.util.MessageToClient; import org.junit.Ignore; /** @@ -797,6 +798,11 @@ public class TestPlayer implements Player { @Override public boolean chooseUse(Outcome outcome, String message, Ability source, Game game) { + return this.chooseUse(outcome, new MessageToClient(message), source, game); + } + + @Override + public boolean chooseUse(Outcome outcome, MessageToClient message, Ability source, Game game) { if (!choices.isEmpty()) { if (choices.get(0).equals("No")) { choices.remove(0); @@ -1897,6 +1903,11 @@ public class TestPlayer implements Player { computerPlayer.pickCard(cards, deck, draft); } + @Override + public boolean scry(int value, Ability source, Game game) { + return computerPlayer.scry(value, source, game); + } + public void setAIPlayer(boolean AIPlayer) { this.AIPlayer = AIPlayer; } diff --git a/Mage/src/mage/abilities/common/AllyEntersBattlefieldTriggeredAbility.java b/Mage/src/mage/abilities/common/AllyEntersBattlefieldTriggeredAbility.java index 341e1161952..9252d5d9e0d 100644 --- a/Mage/src/mage/abilities/common/AllyEntersBattlefieldTriggeredAbility.java +++ b/Mage/src/mage/abilities/common/AllyEntersBattlefieldTriggeredAbility.java @@ -27,14 +27,12 @@ */ package mage.abilities.common; -import java.util.UUID; -import mage.constants.Zone; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.Effect; +import mage.constants.Zone; import mage.game.Game; +import mage.game.events.EntersTheBattlefieldEvent; import mage.game.events.GameEvent; -import mage.game.events.GameEvent.EventType; -import mage.game.permanent.Permanent; /** * @@ -57,11 +55,10 @@ public class AllyEntersBattlefieldTriggeredAbility extends TriggeredAbilityImpl @Override public boolean checkTrigger(GameEvent event, Game game) { - UUID targetId = event.getTargetId(); - Permanent permanent = game.getPermanent(targetId); - if (permanent.getControllerId().equals(this.controllerId) - && (targetId.equals(this.getSourceId()) - || (permanent.hasSubtype("Ally") && !targetId.equals(this.getSourceId())))) { + EntersTheBattlefieldEvent ebe = (EntersTheBattlefieldEvent) event; + if (ebe.getTarget().getControllerId().equals(this.controllerId) + && (event.getTargetId().equals(this.getSourceId()) + || (ebe.getTarget().hasSubtype("Ally") && !event.getTargetId().equals(this.getSourceId())))) { return true; } return false; @@ -69,7 +66,7 @@ public class AllyEntersBattlefieldTriggeredAbility extends TriggeredAbilityImpl @Override public String getRule() { - return "Whenever {this} or another Ally enters the battlefield under your control, " + super.getRule(); + return "Rally — Whenever {this} or another Ally enters the battlefield under your control, " + super.getRule(); } @Override diff --git a/Mage/src/mage/abilities/effects/keyword/ScryEffect.java b/Mage/src/mage/abilities/effects/keyword/ScryEffect.java index 0fc7e4ad760..dec42b05dae 100644 --- a/Mage/src/mage/abilities/effects/keyword/ScryEffect.java +++ b/Mage/src/mage/abilities/effects/keyword/ScryEffect.java @@ -1,16 +1,16 @@ /* * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. - * + * * Redistribution and use in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: - * + * * 1. Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. - * + * * 2. Redistributions in binary form must reproduce the above copyright notice, this list * of conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. - * + * * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR @@ -20,26 +20,19 @@ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * + * * The views and conclusions contained in the software and documentation are those of the * authors and should not be interpreted as representing official policies, either expressed * or implied, of BetaSteward_at_googlemail.com. */ - package mage.abilities.effects.keyword; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; -import mage.cards.Card; -import mage.cards.Cards; -import mage.cards.CardsImpl; import mage.constants.Outcome; -import mage.constants.Zone; import mage.filter.FilterCard; import mage.game.Game; -import mage.game.events.GameEvent; import mage.players.Player; -import mage.target.TargetCard; import mage.util.CardUtil; /** @@ -67,33 +60,7 @@ public class ScryEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player player = game.getPlayer(source.getControllerId()); if (player != null) { - boolean revealed = player.isTopCardRevealed(); // by looking at the cards with scry you have not to reveal the next card - player.setTopCardRevealed(false); - Cards cards = new CardsImpl(); - int count = Math.min(scryNumber, player.getLibrary().size()); - if (count == 0) { - return true; - } - for (int i = 0; i < count; i++) { - Card card = player.getLibrary().removeFromTop(game); - cards.add(card); - } - TargetCard target1 = new TargetCard(Zone.LIBRARY, filter1); - target1.setRequired(false); - // move cards to the bottom of the library - while (player.canRespond() && cards.size() > 0 && player.choose(Outcome.Detriment, cards, target1, game)) { - Card card = cards.get(target1.getFirstTarget(), game); - if (card != null) { - cards.remove(card); - player.moveCardToLibraryWithInfo(card, source.getSourceId(), game, Zone.LIBRARY, false, false); - } - target1.clearChosen(); - } - // move cards to the top of the library - player.putCardsOnTopOfLibrary(cards, game, source, true); - game.fireEvent(new GameEvent(GameEvent.EventType.SCRY, source.getControllerId(), source.getSourceId(), source.getControllerId())); - player.setTopCardRevealed(revealed); - return true; + return player.scry(scryNumber, source, game); } return false; } diff --git a/Mage/src/mage/game/GameImpl.java b/Mage/src/mage/game/GameImpl.java index dbaf2085082..92014988e09 100644 --- a/Mage/src/mage/game/GameImpl.java +++ b/Mage/src/mage/game/GameImpl.java @@ -875,13 +875,14 @@ public abstract class GameImpl implements Game, Serializable { } //20091005 - 103.3 + int startingHandSize = 7; for (UUID playerId : state.getPlayerList(startingPlayerId)) { Player player = getPlayer(playerId); if (!gameOptions.testMode || player.getLife() == 0) { player.initLife(this.getLife()); } if (!gameOptions.testMode) { - player.drawCards(7, this); + player.drawCards(startingHandSize, this); } } @@ -923,6 +924,15 @@ public abstract class GameImpl implements Game, Serializable { } saveState(false); } while (!mulliganPlayers.isEmpty()); + // new scry rule + for (UUID playerId : state.getPlayerList(startingPlayerId)) { + Player player = getPlayer(playerId); + if (player != null && player.getHand().size() < startingHandSize) { + if (player.chooseUse(Outcome.Benefit, new MessageToClient("Scry 1?", "Look at the top card of your library. You may put that card on the bottom of your library."), null, this)) { + player.scry(1, null, this); + } + } + } getState().setChoosingPlayerId(null); Watchers watchers = state.getWatchers(); // add default watchers @@ -1085,15 +1095,6 @@ public abstract class GameImpl implements Game, Serializable { player.drawCards(numCards - deduction, this); } -// @Override -// public void quit(UUID playerId) { -// if (state != null) { -// Player player = state.getPlayer(playerId); -// if (player != null && player.isInGame()) { -// player.quit(this); -// } -// } -// } @Override public synchronized void timerTimeout(UUID playerId) { Player player = state.getPlayer(playerId); diff --git a/Mage/src/mage/players/Player.java b/Mage/src/mage/players/Player.java index 5c960e650c2..094550b16f6 100644 --- a/Mage/src/mage/players/Player.java +++ b/Mage/src/mage/players/Player.java @@ -74,6 +74,7 @@ import mage.target.TargetAmount; import mage.target.TargetCard; import mage.target.common.TargetCardInLibrary; import mage.util.Copyable; +import mage.util.MessageToClient; /** * @@ -451,6 +452,8 @@ public interface Player extends MageItem, Copyable { boolean chooseUse(Outcome outcome, String message, Ability source, Game game); + boolean chooseUse(Outcome outcome, MessageToClient message, Ability source, Game game); + boolean choose(Outcome outcome, Choice choice, Game game); boolean choosePile(Outcome outcome, String message, List pile1, List pile2, Game game); @@ -661,6 +664,7 @@ public interface Player extends MageItem, Copyable { * @param exileName name of exile zone (optional) * @param sourceId * @param game + * @param fromZone * @param withName * @return */ @@ -786,4 +790,6 @@ public interface Player extends MageItem, Copyable { void setMatchPlayer(MatchPlayer matchPlayer); MatchPlayer getMatchPlayer(); + + boolean scry(int value, Ability source, Game game); } diff --git a/Mage/src/mage/players/PlayerImpl.java b/Mage/src/mage/players/PlayerImpl.java index dbcd6373b2e..be796054a89 100644 --- a/Mage/src/mage/players/PlayerImpl.java +++ b/Mage/src/mage/players/PlayerImpl.java @@ -841,7 +841,7 @@ public abstract class PlayerImpl implements Player, Serializable { if (cards.size() != 0) { if (!anyOrder) { for (UUID objectId : cards) { - moveObjectToLibrary(objectId, source.getSourceId(), game, false, false); + moveObjectToLibrary(objectId, source == null ? null : source.getSourceId(), game, false, false); } } else { TargetCard target = new TargetCard(Zone.PICK, new FilterCard("card to put on the bottom of your library (last one chosen will be bottommost)")); @@ -850,11 +850,11 @@ public abstract class PlayerImpl implements Player, Serializable { this.choose(Outcome.Neutral, cards, target, game); UUID targetObjectId = target.getFirstTarget(); cards.remove(targetObjectId); - moveObjectToLibrary(targetObjectId, source.getSourceId(), game, false, false); + moveObjectToLibrary(targetObjectId, source == null ? null : source.getSourceId(), game, false, false); target.clearChosen(); } if (cards.size() == 1) { - moveObjectToLibrary(cards.iterator().next(), source.getSourceId(), game, false, false); + moveObjectToLibrary(cards.iterator().next(), source == null ? null : source.getSourceId(), game, false, false); } } } @@ -875,9 +875,10 @@ public abstract class PlayerImpl implements Player, Serializable { Cards cards = new CardsImpl(cardsToLibrary); // prevent possible ConcurrentModificationException cards.addAll(cardsToLibrary); if (cards.size() != 0) { + UUID sourceId = (source == null ? null : source.getSourceId()); if (!anyOrder) { for (UUID cardId : cards) { - moveObjectToLibrary(cardId, source.getSourceId(), game, true, false); + moveObjectToLibrary(cardId, sourceId, game, true, false); } } else { TargetCard target = new TargetCard(Zone.PICK, new FilterCard("card to put on the top of your library (last one chosen will be topmost)")); @@ -886,11 +887,11 @@ public abstract class PlayerImpl implements Player, Serializable { this.choose(Outcome.Neutral, cards, target, game); UUID targetObjectId = target.getFirstTarget(); cards.remove(targetObjectId); - moveObjectToLibrary(targetObjectId, source.getSourceId(), game, true, false); + moveObjectToLibrary(targetObjectId, sourceId, game, true, false); target.clearChosen(); } if (cards.size() == 1) { - moveObjectToLibrary(cards.iterator().next(), source.getSourceId(), game, true, false); + moveObjectToLibrary(cards.iterator().next(), sourceId, game, true, false); } } } @@ -3284,4 +3285,27 @@ public abstract class PlayerImpl implements Player, Serializable { public void abortReset() { abort = false; } + + @Override + public boolean scry(int value, Ability source, Game game) { + game.informPlayers(getLogName() + " scries " + value); + Cards cards = new CardsImpl(); + cards.addAll(getLibrary().getTopCards(game, value)); + if (!cards.isEmpty()) { + String text; + if (cards.size() == 1) { + text = "card if you want to put it to the bottom of your library (Scry)"; + } else { + text = "cards you want to put on the bottom of your library (Scry)"; + } + TargetCard target = new TargetCard(0, cards.size(), Zone.LIBRARY, new FilterCard(text)); + chooseTarget(Outcome.Benefit, cards, target, source, game); + putCardsOnBottomOfLibrary(new CardsImpl(target.getTargets()), game, source, true); + cards.removeAll(target.getTargets()); + putCardsOnTopOfLibrary(cards, game, source, true); + } + game.fireEvent(new GameEvent(GameEvent.EventType.SCRY, getId(), source == null ? null : source.getSourceId(), getId(), value, true)); + return true; + } + } diff --git a/Mage/src/mage/util/MessageToClient.java b/Mage/src/mage/util/MessageToClient.java index fca0e289063..556bac450d4 100644 --- a/Mage/src/mage/util/MessageToClient.java +++ b/Mage/src/mage/util/MessageToClient.java @@ -40,4 +40,17 @@ public class MessageToClient { public String getHintText() { return hintText; } + + public void setMessage(String message) { + this.message = message; + } + + public void setSecondMessage(String secondMessage) { + this.secondMessage = secondMessage; + } + + public void setHintText(String hintText) { + this.hintText = hintText; + } + }