diff --git a/Mage.Sets/src/mage/sets/magic2013/OdricMasterTactician.java b/Mage.Sets/src/mage/sets/magic2013/OdricMasterTactician.java index 2a2985edc55..c52517840c8 100644 --- a/Mage.Sets/src/mage/sets/magic2013/OdricMasterTactician.java +++ b/Mage.Sets/src/mage/sets/magic2013/OdricMasterTactician.java @@ -146,13 +146,17 @@ class OdricMasterTacticianEffect extends ReplacementEffectImpl> extends public abstract boolean mustBlock(Game game); + public boolean mustBlockAny(Game game) { + return false; + } + public UUID mustAttackDefender(Ability source, Game game) { return null; } diff --git a/Mage/src/mage/abilities/effects/common/BlocksIfAbleTargetEffect.java b/Mage/src/mage/abilities/effects/common/BlocksIfAbleTargetEffect.java new file mode 100644 index 00000000000..22431a3fe01 --- /dev/null +++ b/Mage/src/mage/abilities/effects/common/BlocksIfAbleTargetEffect.java @@ -0,0 +1,89 @@ +/* + * 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.effects.common; + +import mage.Constants.Duration; +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.effects.RequirementEffect; +import mage.game.Game; +import mage.game.permanent.Permanent; + +/** + * + * @author magenoxx_at_googlemail.com + */ +public class BlocksIfAbleTargetEffect extends RequirementEffect { + + public BlocksIfAbleTargetEffect(Duration duration) { + super(duration); + } + + public BlocksIfAbleTargetEffect(final BlocksIfAbleTargetEffect effect) { + super(effect); + } + + @Override + public BlocksIfAbleTargetEffect copy() { + return new BlocksIfAbleTargetEffect(this); + } + + @Override + public boolean applies(Permanent permanent, Ability source, Game game) { + Permanent creature = game.getPermanent(source.getFirstTarget()); + if (creature != null && creature.getId().equals(permanent.getId())) { + return true; + } + return false; + } + + @Override + public boolean mustAttack(Game game) { + return false; + } + + @Override + public boolean mustBlock(Game game) { + return false; + } + + @Override + public boolean mustBlockAny(Game game) { + return true; + } + + @Override + public String getText(Mode mode) { + if (this.duration == Duration.EndOfTurn) + return "Target " + mode.getTargets().get(0).getTargetName() + " blocks this turn if able"; + else + return "Target " + mode.getTargets().get(0).getTargetName() + " blocks each turn if able"; + } + +} \ No newline at end of file diff --git a/Mage/src/mage/abilities/effects/common/DamageTargetEffect.java b/Mage/src/mage/abilities/effects/common/DamageTargetEffect.java index 3ec2abe784d..05b7841b766 100644 --- a/Mage/src/mage/abilities/effects/common/DamageTargetEffect.java +++ b/Mage/src/mage/abilities/effects/common/DamageTargetEffect.java @@ -109,7 +109,6 @@ public class DamageTargetEffect extends OneShotEffect { } Player player = game.getPlayer(targetPointer.getFirst(game, source)); if (player != null) { - game.informPlayer(player, "You were dealt 3 damage"); player.damage(amount.calculate(game, source), source.getSourceId(), game, false, preventable); return true; } diff --git a/Mage/src/mage/abilities/effects/common/continious/GainAbilityTargetEffect.java b/Mage/src/mage/abilities/effects/common/continious/GainAbilityTargetEffect.java index cedc0457745..946f6aa5fe0 100644 --- a/Mage/src/mage/abilities/effects/common/continious/GainAbilityTargetEffect.java +++ b/Mage/src/mage/abilities/effects/common/continious/GainAbilityTargetEffect.java @@ -48,6 +48,8 @@ import java.util.UUID; public class GainAbilityTargetEffect extends ContinuousEffectImpl { protected Ability ability; + protected String rule; + protected boolean hasRule; public GainAbilityTargetEffect(Ability ability, Duration duration) { super(duration, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, @@ -55,9 +57,19 @@ public class GainAbilityTargetEffect extends ContinuousEffectImpl 0 ? ability.getEffects().get(0).getOutcome() : Outcome.AddAbility); + this.ability = ability; + this.rule = rule; + this.hasRule = true; + } + public GainAbilityTargetEffect(final GainAbilityTargetEffect effect) { super(effect); this.ability = effect.ability.copy(); + this.rule = effect.rule; + this.hasRule = effect.hasRule; } @Override @@ -86,6 +98,9 @@ public class GainAbilityTargetEffect extends ContinuousEffectImpl 1){ diff --git a/Mage/src/mage/abilities/keyword/AttacksThisTurnMarkerAbility.java b/Mage/src/mage/abilities/keyword/AttacksThisTurnMarkerAbility.java new file mode 100644 index 00000000000..5b251ba3b29 --- /dev/null +++ b/Mage/src/mage/abilities/keyword/AttacksThisTurnMarkerAbility.java @@ -0,0 +1,69 @@ +/* + * 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 mage.Constants.Zone; +import mage.abilities.MageSingleton; +import mage.abilities.StaticAbility; + +import java.io.ObjectStreamException; + +/** + * This is marker ability that is used only for rule display. + * You should use {@see AttacksIfAbleTargetEffect} for real effect. + * + * @author magenoxx_at_googlemail.com + */ +public class AttacksThisTurnMarkerAbility extends StaticAbility implements MageSingleton { + + private static final AttacksThisTurnMarkerAbility fINSTANCE = new AttacksThisTurnMarkerAbility(); + + private Object readResolve() throws ObjectStreamException { + return fINSTANCE; + } + + public static AttacksThisTurnMarkerAbility getInstance() { + return fINSTANCE; + } + + private AttacksThisTurnMarkerAbility() { + super(Zone.BATTLEFIELD, null); + } + + @Override + public String getRule() { + return "{this} attacks this turn if able"; + } + + @Override + public AttacksThisTurnMarkerAbility copy() { + return fINSTANCE; + } + +} diff --git a/Mage/src/mage/abilities/keyword/BlocksThisTurnMarkerAbility.java b/Mage/src/mage/abilities/keyword/BlocksThisTurnMarkerAbility.java new file mode 100644 index 00000000000..81e3b4bd30e --- /dev/null +++ b/Mage/src/mage/abilities/keyword/BlocksThisTurnMarkerAbility.java @@ -0,0 +1,69 @@ +/* + * 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 mage.Constants.Zone; +import mage.abilities.MageSingleton; +import mage.abilities.StaticAbility; + +import java.io.ObjectStreamException; + +/** + * This is marker ability that is used only for rule display. + * You should use {@see BlocksIfAbleTargetEffect} for real effect. + * + * @author magenoxx_at_googlemail.com + */ +public class BlocksThisTurnMarkerAbility extends StaticAbility implements MageSingleton { + + private static final BlocksThisTurnMarkerAbility fINSTANCE = new BlocksThisTurnMarkerAbility(); + + private Object readResolve() throws ObjectStreamException { + return fINSTANCE; + } + + public static BlocksThisTurnMarkerAbility getInstance() { + return fINSTANCE; + } + + private BlocksThisTurnMarkerAbility() { + super(Zone.BATTLEFIELD, null); + } + + @Override + public String getRule() { + return "{this} blocks this turn if able"; + } + + @Override + public BlocksThisTurnMarkerAbility copy() { + return fINSTANCE; + } + +} diff --git a/Mage/src/mage/game/combat/Combat.java b/Mage/src/mage/game/combat/Combat.java index 4497aa291f7..2d7a1d1dc8f 100644 --- a/Mage/src/mage/game/combat/Combat.java +++ b/Mage/src/mage/game/combat/Combat.java @@ -32,6 +32,7 @@ import mage.Constants.Outcome; import mage.abilities.effects.RequirementEffect; import mage.abilities.keyword.CantAttackAloneAbility; import mage.abilities.keyword.VigilanceAbility; +import mage.filter.common.FilterControlledCreaturePermanent; import mage.filter.common.FilterCreatureForCombat; import mage.filter.common.FilterPlaneswalkerPermanent; import mage.game.Game; @@ -195,12 +196,18 @@ public class Combat implements Serializable, Copyable { if (!game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.DECLARING_BLOCKERS, attackerId, attackerId))) { Player player = game.getPlayer(attackerId); //20101001 - 509.1c - checkBlockRequirements(player, game); + checkBlockRequirementsBefore(player, game); for (UUID defenderId : getPlayerDefenders(game)) { - game.getPlayer(defenderId).selectBlockers(game, defenderId); - if (game.isPaused() || game.isGameOver()) - return; - checkBlockRestrictions(game.getPlayer(defenderId), game); + boolean choose = true; + Player defender = game.getPlayer(defenderId); + while (choose) { + game.getPlayer(defenderId).selectBlockers(game, defenderId); + if (game.isPaused() || game.isGameOver()) { + return; + } + checkBlockRestrictions(game.getPlayer(defenderId), game); + choose = !checkBlockRequirementsAfter(defender, defender, game); + } game.fireEvent(GameEvent.getEvent(GameEvent.EventType.DECLARED_BLOCKERS, defenderId, defenderId)); } } @@ -229,9 +236,8 @@ public class Combat implements Serializable, Copyable { } } - public void checkBlockRequirements(Player player, Game game) { + public void checkBlockRequirementsBefore(Player player, Game game) { //20101001 - 509.1c - //TODO: handle case where more than one attacker must be blocked for (Permanent creature : game.getBattlefield().getActivePermanents(filterBlockers, player.getId(), game)) { if (game.getOpponents(attackerId).contains(creature.getControllerId())) { for (RequirementEffect effect : game.getContinuousEffects().getApplicableRequirementEffects(creature, game)) { @@ -247,6 +253,43 @@ public class Combat implements Serializable, Copyable { } } + public boolean checkBlockRequirementsAfter(Player player, Player controller, Game game) { + //20101001 - 509.1c + for (Permanent creature : game.getBattlefield().getActivePermanents(new FilterControlledCreaturePermanent(), player.getId(), game)) { + if (creature.getBlocking() == 0 && game.getOpponents(attackerId).contains(creature.getControllerId())) { + for (RequirementEffect effect : game.getContinuousEffects().getApplicableRequirementEffects(creature, game)) { + if (effect.mustBlockAny(game)) { + // check that it can block an attacker + boolean mayBlock = false; + for (UUID attackingCreatureId : getAttackers()) { + if (creature.canBlock(attackingCreatureId, game)) { + mayBlock = true; + break; + } + } + if (mayBlock) { + if (controller.isHuman()) { + game.informPlayer(controller, "Creature should block this turn: " + creature.getName()); + } else { + Player defender = game.getPlayer(creature.getControllerId()); + if (defender != null) { + for (UUID attackingCreatureId : getAttackers()) { + if (creature.canBlock(attackingCreatureId, game)) { + defender.declareBlocker(creature.getId(), attackingCreatureId, game); + break; + } + } + } + } + return false; + } + } + } + } + } + return true; + } + public void setDefenders(Game game) { Set opponents = game.getOpponents(attackerId); PlayerList players;