diff --git a/Mage/src/mage/abilities/keyword/NinjutsuAbility.java b/Mage/src/mage/abilities/keyword/NinjutsuAbility.java new file mode 100644 index 0000000000..27dd017d28 --- /dev/null +++ b/Mage/src/mage/abilities/keyword/NinjutsuAbility.java @@ -0,0 +1,218 @@ +/* +* 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 mage.abilities.keyword; + +import java.util.UUID; +import mage.Constants; +import mage.Constants.Zone; +import mage.abilities.Ability; +import mage.abilities.ActivatedAbilityImpl; +import mage.abilities.costs.Cost; +import mage.abilities.costs.CostImpl; +import mage.abilities.costs.mana.ManaCost; +import mage.abilities.effects.OneShotEffect; +import mage.cards.Card; +import mage.cards.Cards; +import mage.cards.CardsImpl; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.permanent.UnblockedPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetControlledCreaturePermanent; +import mage.target.common.TargetControlledPermanent; + +/** + * 702.47. Ninjutsu + * + * 702.47a Ninjutsu is an activated ability that functions only while the card + * with ninjutsu is in a player's hand. "Ninjutsu [cost]" means "[Cost], Reveal + * this card from your hand, Return an unblocked attacking creature you control + * to its owner's hand: Put this card onto the battlefield from your hand tapped + * and attacking." + * + * 702.47b The card with ninjutsu remains revealed from the time the ability is + * announced until the ability leaves the stack. + * + * 702.47c A ninjutsu ability may be activated only while a creature on the + * battlefield is unblocked (see rule 509.1h). The creature with ninjutsu is + * put onto the battlefield unblocked. It will be attacking the same player or + * planeswalker as the creature that was returned to its owner's hand. + * + * @param cost ninjutsu mana cost + * + * @author LevelX2 + */ +public class NinjutsuAbility extends ActivatedAbilityImpl { + + private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("unblocked attacker you control"); + + static { + filter.add(new UnblockedPredicate()); + } + + public NinjutsuAbility(ManaCost manaCost) { + super(Zone.HAND,new NinjutsuEffect(), manaCost); + this.addCost(new RevealNinjutsuCardCost()); + this.addCost(new ReturnAttackerToHandTargetCost(new TargetControlledCreaturePermanent(1,1,filter,false))); + } + + public NinjutsuAbility(NinjutsuAbility ability) { + super(ability); + } + + @Override + public NinjutsuAbility copy() { + return new NinjutsuAbility(this); + } + + @Override + public String getRule() { + return "Ninjutsu " + getManaCostsToPay().getText()+ " (" + getManaCostsToPay().getText() +" Return an unblocked attacker you control to hand: Put this card onto the battlefield from your hand tapped and attacking.)"; + } +} + +class NinjutsuEffect extends OneShotEffect { + + public NinjutsuEffect() { + super(Constants.Outcome.PutCreatureInPlay); + this.staticText = "Put this card onto the battlefield from your hand tapped and attacking"; + } + + public NinjutsuEffect(final NinjutsuEffect effect) { + super(effect); + } + + @Override + public NinjutsuEffect copy() { + return new NinjutsuEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Card card = game.getCard(source.getSourceId()); + if (card != null) { + card.putOntoBattlefield(game, Constants.Zone.HAND, source.getId(), source.getControllerId()); + Permanent permanent = game.getPermanent(source.getSourceId()); + if (permanent != null) { + UUID defendingPlayerId = null; + for (Cost cost :source.getCosts()) { + if (cost instanceof ReturnAttackerToHandTargetCost) { + defendingPlayerId = ((ReturnAttackerToHandTargetCost) cost).getDefendingPlayerId(); + } + } + if (defendingPlayerId != null) { + game.getCombat().declareAttacker(permanent.getId(), defendingPlayerId, game); + permanent.setTapped(true); + return true; + } + } + } + return false; + } +} + +class ReturnAttackerToHandTargetCost extends CostImpl { + + private UUID defendingPlayerId; + + public ReturnAttackerToHandTargetCost(TargetControlledPermanent target) { + this.addTarget(target); + this.defendingPlayerId = null; + this.text = "Return an unblocked attacker you control to hand"; + } + + public ReturnAttackerToHandTargetCost(ReturnAttackerToHandTargetCost cost) { + super(cost); + } + + @Override + public boolean pay(Ability ability, Game game, UUID sourceId, UUID controllerId, boolean noMana) { + if (targets.choose(Constants.Outcome.ReturnToHand, controllerId, sourceId, game)) { + for (UUID targetId: targets.get(0).getTargets()) { + Permanent permanent = game.getPermanent(targetId); + if (permanent == null) { + return false; + } + defendingPlayerId = game.getCombat().getDefendingPlayer(permanent.getId()); + paid |= permanent.moveToZone(Zone.HAND, sourceId, game, false); + } + } + return paid; + } + + @Override + public boolean canPay(UUID sourceId, UUID controllerId, Game game) { + return targets.canChoose(controllerId, game); + } + + @Override + public ReturnAttackerToHandTargetCost copy() { + return new ReturnAttackerToHandTargetCost(this); + } + + public UUID getDefendingPlayerId() { + return defendingPlayerId; + } +} + +class RevealNinjutsuCardCost extends CostImpl { + + public RevealNinjutsuCardCost() { + this.text = "reveal ninjutsu card"; + } + + public RevealNinjutsuCardCost(RevealNinjutsuCardCost cost) { + super(cost); + } + + @Override + public boolean pay(Ability ability, Game game, UUID sourceId, UUID controllerId, boolean noMana) { + Player player = game.getPlayer(controllerId); + + Card card = player.getHand().get(ability.getSourceId(), game); + if (card != null) { + Cards cards = new CardsImpl(card); + player.revealCards("Ninjutsu", cards, game); + paid = true; + return paid; + } + return false; + } + + @Override + public boolean canPay(UUID sourceId, UUID controllerId, Game game) { + return true; + } + + @Override + public RevealNinjutsuCardCost copy() { + return new RevealNinjutsuCardCost(this); + } +} diff --git a/Mage/src/mage/abilities/keyword/OfferingAbility.java b/Mage/src/mage/abilities/keyword/OfferingAbility.java index 4a697bb1f3..b1c59546f7 100644 --- a/Mage/src/mage/abilities/keyword/OfferingAbility.java +++ b/Mage/src/mage/abilities/keyword/OfferingAbility.java @@ -69,7 +69,6 @@ import mage.util.CardUtil; * match colored mana in the colored mana cost of the card with offering, or is in excess * of the card's colored mana cost, reduces that much generic mana in the total cost. # * - * @param card card that gains offering * @param subtype name of the subtype that can be offered * * @author LevelX2 @@ -77,7 +76,6 @@ import mage.util.CardUtil; public class OfferingAbility extends StaticAbility { private FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent(); - private Boolean creatureOffered = false; public OfferingAbility(String subtype) { super(Zone.ALL, null); @@ -89,7 +87,6 @@ public class OfferingAbility extends StaticAbility { public OfferingAbility(OfferingAbility ability) { super(ability); this.filter = ability.filter; - this.creatureOffered = ability.creatureOffered; } @Override @@ -101,20 +98,11 @@ public class OfferingAbility extends StaticAbility { return filter; } - public void setCreatureOffered(Boolean creatureOffered) { - this.creatureOffered = creatureOffered; - } - - public Boolean isCreatureOffered() { - return creatureOffered; - } - @Override public String getRule(boolean all) { String subtype = filter.getMessage(); return subtype + " offering (You may cast this card any time you could cast an instant by sacrificing a " + subtype + " and paying the difference in mana costs between this and the sacrificed " + subtype + ". Mana cost includes color.)"; } - } class OfferingAsThoughEffect extends AsThoughEffectImpl { diff --git a/Mage/src/mage/filter/predicate/permanent/UnblockedPredicate.java b/Mage/src/mage/filter/predicate/permanent/UnblockedPredicate.java new file mode 100644 index 0000000000..f2391fe5ae --- /dev/null +++ b/Mage/src/mage/filter/predicate/permanent/UnblockedPredicate.java @@ -0,0 +1,64 @@ +/* + * 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 mage.filter.predicate.permanent; + +import mage.Constants.PhaseStep; +import mage.filter.predicate.Predicate; +import mage.game.Game; +import mage.game.combat.CombatGroup; +import mage.game.permanent.Permanent; +import mage.game.turn.Step; + +/** + * Checks if an attacking creature is unblocked after the + * declare blockers step. + * + * @author LevelX2 + */ +public class UnblockedPredicate implements Predicate { + + @Override + public boolean apply(Permanent input, Game game) { + if (input.isAttacking()) { + if ((game.getPhase().getStep().getType() == PhaseStep.DECLARE_BLOCKERS + || game.getPhase().getStep().getType() == PhaseStep.COMBAT_DAMAGE) + && game.getStep().getStepPart() == Step.StepPart.PRIORITY) { + CombatGroup combatGroup = game.getCombat().findGroup(input.getId()); + if (combatGroup != null) { + return combatGroup.getBlockers().isEmpty(); + } + } + } + return false; + } + + @Override + public String toString() { + return "Unblocked"; + } +}