From f32b28abccc485113bd91dbebbc1b08178aaa877 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Thu, 30 Jan 2014 15:01:25 +0100 Subject: [PATCH] * Tromokratis - Added check that blockers can't be selected to block if they are not able to block anyway. Some redesign of combat. --- .../mage/sets/bornofthegods/Tromokratis.java | 38 ++++++ .../src/mage/sets/legions/HunterSliver.java | 13 +- .../abilities/effects/ContinuousEffects.java | 3 + .../abilities/effects/RestrictionEffect.java | 2 +- Mage/src/mage/game/combat/Combat.java | 115 ++++++++++-------- .../mage/game/permanent/PermanentImpl.java | 26 ++-- 6 files changed, 128 insertions(+), 69 deletions(-) diff --git a/Mage.Sets/src/mage/sets/bornofthegods/Tromokratis.java b/Mage.Sets/src/mage/sets/bornofthegods/Tromokratis.java index 83b9a0b70d1..b3c316dd628 100644 --- a/Mage.Sets/src/mage/sets/bornofthegods/Tromokratis.java +++ b/Mage.Sets/src/mage/sets/bornofthegods/Tromokratis.java @@ -27,6 +27,8 @@ */ package mage.sets.bornofthegods; +import java.util.HashSet; +import java.util.Map; import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; @@ -39,6 +41,7 @@ import mage.abilities.effects.RestrictionEffect; import mage.abilities.effects.common.continious.GainAbilitySourceEffect; import mage.abilities.keyword.HexproofAbility; import mage.cards.CardImpl; +import mage.constants.AsThoughEffectType; import mage.constants.CardType; import mage.constants.Duration; import mage.constants.Rarity; @@ -90,6 +93,8 @@ public class Tromokratis extends CardImpl { class CantBeBlockedUnlessAllEffect extends RestrictionEffect { + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent(); + public CantBeBlockedUnlessAllEffect() { super(Duration.WhileOnBattlefield); staticText = "{this} can't be blocked unless all creatures defending player controls block it"; @@ -104,6 +109,39 @@ class CantBeBlockedUnlessAllEffect extends RestrictionEffect> entry: game.getContinuousEffects().getApplicableRestrictionEffects(permanent, game).entrySet()) { + for (Ability ability : entry.getValue()) { + if (!entry.getKey().canBlock(attacker, permanent, ability, game)) { + return false; + } + } + } + // check also attacker's restriction effects + for (Map.Entry> restrictionEntry: game.getContinuousEffects().getApplicableRestrictionEffects(attacker, game).entrySet()) { + for (Ability ability : restrictionEntry.getValue()) { + if (!(restrictionEntry.getKey() instanceof CantBeBlockedUnlessAllEffect) + && !restrictionEntry.getKey().canBeBlocked(attacker, permanent, ability, game)) { + return false; + } + } + } + if (attacker.hasProtectionFrom(permanent, game)) { + return false; + } + } + return true; + } + + @Override public boolean canBeBlockedCheckAfter(Permanent attacker, Ability source, Game game) { for (CombatGroup combatGroup: game.getCombat().getGroups()) { diff --git a/Mage.Sets/src/mage/sets/legions/HunterSliver.java b/Mage.Sets/src/mage/sets/legions/HunterSliver.java index abe51606a1b..4344273e760 100644 --- a/Mage.Sets/src/mage/sets/legions/HunterSliver.java +++ b/Mage.Sets/src/mage/sets/legions/HunterSliver.java @@ -76,8 +76,8 @@ public class HunterSliver extends CardImpl { } class ProvokeEffect extends RequirementEffect { - - public ProvokeEffect() { + + public ProvokeEffect() { this(Duration.EndOfTurn); } @@ -94,16 +94,15 @@ class ProvokeEffect extends RequirementEffect { public boolean applies(Permanent permanent, Ability source, Game game) { if (permanent.getId().equals(targetPointer.getFirst(game, source))) { Permanent blocker = game.getPermanent(source.getFirstTarget()); - if (blocker != null && blocker.isTapped()){ + if (blocker != null && blocker.isTapped()) { blocker.untap(game); - if (blocker.canBlock(source.getSourceId(), game)) { - return true; + if (blocker.canBlock(source.getSourceId(), game)) { + return true; + } } } - } return false; } - @Override public boolean mustAttack(Game game) { diff --git a/Mage/src/mage/abilities/effects/ContinuousEffects.java b/Mage/src/mage/abilities/effects/ContinuousEffects.java index 61f1b089464..bc72a2d3dca 100644 --- a/Mage/src/mage/abilities/effects/ContinuousEffects.java +++ b/Mage/src/mage/abilities/effects/ContinuousEffects.java @@ -919,6 +919,9 @@ public class ContinuousEffects implements Serializable { return texts; } + public boolean existRequirementEffects() { + return !requirementEffects.isEmpty(); + } } class TimestampSorter implements Comparator { @Override diff --git a/Mage/src/mage/abilities/effects/RestrictionEffect.java b/Mage/src/mage/abilities/effects/RestrictionEffect.java index 5dcacdd2afa..53e0053c361 100644 --- a/Mage/src/mage/abilities/effects/RestrictionEffect.java +++ b/Mage/src/mage/abilities/effects/RestrictionEffect.java @@ -76,7 +76,7 @@ public abstract class RestrictionEffect> extends * @param attacker * @param source * @param game - * @return true = block is ok fals = block is not valid (human: back to defining blockers, AI: remove blocker) + * @return true = block is ok false = block is not valid (human: back to defining blockers, AI: remove blocker) */ public boolean canBeBlockedCheckAfter(Permanent attacker, Ability source, Game game) { return true; diff --git a/Mage/src/mage/game/combat/Combat.java b/Mage/src/mage/game/combat/Combat.java index 3acbca19a9f..f0eabc316c7 100644 --- a/Mage/src/mage/game/combat/Combat.java +++ b/Mage/src/mage/game/combat/Combat.java @@ -74,7 +74,7 @@ public class Combat implements Serializable, Copyable { protected Map> numberCreaturesDefenderAttackedBy = new HashMap>(); protected UUID attackerId; //the player that is attacking // > - protected Map> creaturesForcedToBlockAttackers = new HashMap>(); + protected Map> creatureMustBlockAttackers = new HashMap>(); // which creature is forced to attack which defender(s). If set is empty, the creature can attack every possible defender private final Map> creaturesForcedToAttack = new HashMap>(); @@ -141,7 +141,7 @@ public class Combat implements Serializable, Copyable { blockingGroups.clear(); defenders.clear(); attackerId = null; - creaturesForcedToBlockAttackers.clear(); + creatureMustBlockAttackers.clear(); numberCreaturesDefenderAttackedBy.clear(); creaturesForcedToAttack.clear(); maxAttackers = Integer.MIN_VALUE; @@ -306,7 +306,7 @@ public class Combat implements Serializable, Copyable { public void selectBlockers(Player blockController, Game game) { Player attacker = game.getPlayer(attackerId); //20101001 - 509.1c - this.checkBlockRequirementsBefore(attacker, game); + this.retrieveMustBlockAttackerRequirements(attacker, game); for (UUID defenderId : getPlayerDefenders(game)) { Player defender = game.getPlayer(defenderId); if (defender != null) { @@ -415,39 +415,32 @@ public class Combat implements Serializable, Copyable { } } - public void checkBlockRequirementsBefore(Player player, Game game) { - /*20101001 - 509.1c - * 509.1c The defending player checks each creature he or she controls to see whether it's affected by - * any requirements (effects that say a creature must block, or that it must block if some condition is met). - * If the number of requirements that are being obeyed is fewer than the maximum possible number of - * requirements that could be obeyed without disobeying any restrictions, the declaration of blockers is - * illegal. If a creature can't block unless a player pays a cost, that player is not required to pay - * that cost, even if blocking with that creature would increase the number of requirements being obeyed. - * - * Example: A player controls one creature that "blocks if able" and another creature with no abilities. - * An effect states "Creatures can't be blocked except by two or more creatures." Having only the first - * creature block violates the restriction. Having neither creature block fulfills the restriction but - * not the requirement. Having both creatures block the same attacking creature fulfills both the restriction - * and the requirement, so that's the only option. - */ - for (Permanent creature : game.getBattlefield().getActivePermanents(filterBlockers, player.getId(), game)) { - if (game.getOpponents(attackerId).contains(creature.getControllerId())) { - for (Map.Entry entry : game.getContinuousEffects().getApplicableRequirementEffects(creature, game).entrySet()) { - RequirementEffect effect = (RequirementEffect) entry.getKey(); - if (effect.mustBlock(game)) { - for (Ability ability : (HashSet) entry.getValue()) { - UUID attackId = effect.mustBlockAttacker(ability, game); - Player defender = game.getPlayer(creature.getControllerId()); - if (attackId != null && defender != null) { - if (creaturesForcedToBlockAttackers.containsKey(creature.getId())) { - creaturesForcedToBlockAttackers.get(creature.getId()).add(attackId); - } else { - Set forcingAttackers = new HashSet(); - forcingAttackers.add(attackId); - creaturesForcedToBlockAttackers.put(creature.getId(), forcingAttackers); - // assign block to the first forcing attacker automatically - defender.declareBlocker(defender.getId(), creature.getId(), attackId, game); - } + /** + * Retrieves all requirements that apply and creates a Map with blockers and attackers + * // Map> + * + * @param player - attacker + * @param game + */ + private void retrieveMustBlockAttackerRequirements(Player attackingPlayer, Game game) { + if (!game.getContinuousEffects().existRequirementEffects()) { + return; + } + for (Permanent possibleBlocker : game.getBattlefield().getActivePermanents(filterBlockers, attackingPlayer.getId(), game)) { + for (Map.Entry> requirementEntry : game.getContinuousEffects().getApplicableRequirementEffects(possibleBlocker, game).entrySet()) { + if (requirementEntry.getKey().mustBlock(game)) { + for (Ability ability : requirementEntry.getValue()) { + UUID attackingCreatureId = requirementEntry.getKey().mustBlockAttacker(ability, game); + Player defender = game.getPlayer(possibleBlocker.getControllerId()); + if (attackingCreatureId != null && defender != null) { + if (creatureMustBlockAttackers.containsKey(possibleBlocker.getId())) { + creatureMustBlockAttackers.get(possibleBlocker.getId()).add(attackingCreatureId); + } else { + Set forcingAttackers = new HashSet(); + forcingAttackers.add(attackingCreatureId); + creatureMustBlockAttackers.put(possibleBlocker.getId(), forcingAttackers); + // assign block to the first forcing attacker automatically + defender.declareBlocker(defender.getId(), possibleBlocker.getId(), attackingCreatureId, game); } } } @@ -456,6 +449,31 @@ public class Combat implements Serializable, Copyable { } } + /** + * 509.1c The defending player checks each creature he or she controls to + * see whether it's affected by any requirements (effects that say a + * creature must block, or that it must block if some condition is met). If + * the number of requirements that are being obeyed is fewer than the + * maximum possible number of requirements that could be obeyed without + * disobeying any restrictions, the declaration of blockers is illegal. If a + * creature can't block unless a player pays a cost, that player is not + * required to pay that cost, even if blocking with that creature would + * increase the number of requirements being obeyed. + * + * + * Example: A player controls one creature that "blocks if able" and another + * creature with no abilities. An effect states "Creatures can't be blocked + * except by two or more creatures." Having only the first creature block + * violates the restriction. Having neither creature block fulfills the + * restriction but not the requirement. Having both creatures block the same + * attacking creature fulfills both the restriction and the requirement, so + * that's the only option. + * + * @param player + * @param controller + * @param game + * @return + */ public boolean checkBlockRequirementsAfter(Player player, Player controller, Game game) { // Get once a list of all opponents in range Set opponents = game.getOpponents(attackerId); @@ -558,7 +576,7 @@ public class Combat implements Serializable, Copyable { // read through all possible blockers boolean possibleBlockerAvailable = false; for (UUID possibleBlockerId : mustBeBlockedByAtLeastOne.get(toBeBlockedCreatureId)) { - Set forcingAttackers = creaturesForcedToBlockAttackers.get(possibleBlockerId); + Set forcingAttackers = creatureMustBlockAttackers.get(possibleBlockerId); if (forcingAttackers == null) { // no other creature forces the blocker to block -> it's available possibleBlockerAvailable = true; @@ -581,7 +599,7 @@ public class Combat implements Serializable, Copyable { // get attackers forcing the possible blocker to block possibleBlockerAvailable = true; for (UUID blockedAttackerId : blockedAttackers) { - if (creaturesForcedToBlockAttackers.get(possibleBlockerId).contains(blockedAttackerId)) { + if (creatureMustBlockAttackers.get(possibleBlockerId).contains(blockedAttackerId)) { possibleBlockerAvailable = false; break; } @@ -613,7 +631,7 @@ public class Combat implements Serializable, Copyable { } // check if creatures are forced to block but do not block at all or block creatures they are not forced to block StringBuilder sb = new StringBuilder(); - for (Map.Entry> entry : creaturesForcedToBlockAttackers.entrySet()) { + for (Map.Entry> entry : creatureMustBlockAttackers.entrySet()) { boolean blockIsValid; Permanent creatureForcedToBlock = game.getPermanent(entry.getKey()); if (creatureForcedToBlock == null) { @@ -663,13 +681,13 @@ public class Combat implements Serializable, Copyable { } /** - * Checks the canBeBlockedCheckAfter RestrictionEffect - * Is the block still valid after all block decisions are done - * + * Checks the canBeBlockedCheckAfter RestrictionEffect Is the block still + * valid after all block decisions are done + * * @param player * @param controller * @param game - * @return + * @return */ public boolean checkBlockRestrictionsAfter(Player player, Player controller, Game game) { for (UUID attackingCreatureId : this.getAttackers()) { @@ -680,27 +698,26 @@ public class Combat implements Serializable, Copyable { for (Ability ability : (HashSet) entry.getValue()) { if (!effect.canBeBlockedCheckAfter(attackingCreature, ability, game)) { if (controller.isHuman()) { - game.informPlayer(controller, new StringBuilder(attackingCreature.getName()).append(" can't be blocked this way." ).toString()); + game.informPlayer(controller, new StringBuilder(attackingCreature.getName()).append(" can't be blocked this way.").toString()); return false; } else { // remove blocking creatures for AI - for (CombatGroup combatGroup: this.getGroups()) { + for (CombatGroup combatGroup : this.getGroups()) { if (combatGroup.getAttackers().contains(attackingCreatureId)) { - for(UUID blockerId :combatGroup.getBlockers()) { + for (UUID blockerId : combatGroup.getBlockers()) { removeBlocker(blockerId, game); } } } - + } } } } - } + } } return true; } - public void setDefenders(Game game) { Set opponents = game.getOpponents(attackerId); diff --git a/Mage/src/mage/game/permanent/PermanentImpl.java b/Mage/src/mage/game/permanent/PermanentImpl.java index 2e7b37183c7..1f7817b2bb1 100644 --- a/Mage/src/mage/game/permanent/PermanentImpl.java +++ b/Mage/src/mage/game/permanent/PermanentImpl.java @@ -760,7 +760,7 @@ public abstract class PermanentImpl> extends CardImpl return false; } if (abilities.containsKey(HexproofAbility.getInstance().getId())) { - if (game.getOpponents(controllerId).contains(sourceControllerId) && + if (game.getPlayer(this.getControllerId()).hasOpponent(sourceControllerId, game) && !game.getContinuousEffects().asThough(this.getId(), AsThoughEffectType.HEXPROOF, game)) { return false; } @@ -885,29 +885,31 @@ public abstract class PermanentImpl> extends CardImpl return false; } Permanent attacker = game.getPermanent(attackerId); - // controller of attacking permanent must be an opponent - if (game.getOpponents(this.getControllerId()) != null && !game.getOpponents(this.getControllerId()).contains(attacker.getControllerId())) { + if (attacker == null) { + return false; + } + // controller of attacking permanent must be an opponent + if (!game.getPlayer(this.getControllerId()).hasOpponent(attacker.getControllerId(), game)) { return false; } //20101001 - 509.1b - for (Map.Entry entry: game.getContinuousEffects().getApplicableRestrictionEffects(this, game).entrySet()) { - RestrictionEffect effect = (RestrictionEffect)entry.getKey(); - for (Ability ability : (HashSet) entry.getValue()) { - if (!effect.canBlock(attacker, this, ability, game)) { + // check blocker restrictions + for (Map.Entry> entry: game.getContinuousEffects().getApplicableRestrictionEffects(this, game).entrySet()) { + for (Ability ability : entry.getValue()) { + if (!entry.getKey().canBlock(attacker, this, ability, game)) { return false; } } } // check also attacker's restriction effects - for (Map.Entry entry: game.getContinuousEffects().getApplicableRestrictionEffects(attacker, game).entrySet()) { - RestrictionEffect effect = (RestrictionEffect)entry.getKey(); - for (Ability ability : (HashSet) entry.getValue()) { - if (!effect.canBeBlocked(attacker, this, ability, game)) { + for (Map.Entry> restrictionEntry: game.getContinuousEffects().getApplicableRestrictionEffects(attacker, game).entrySet()) { + for (Ability ability : restrictionEntry.getValue()) { + if (!restrictionEntry.getKey().canBeBlocked(attacker, this, ability, game)) { return false; } } } - return attacker == null || !attacker.hasProtectionFrom(this, game); + return !attacker.hasProtectionFrom(this, game); } @Override