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 273d434d19..51fff34f5c 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 @@ -70,6 +70,7 @@ import mage.filter.common.FilterCreatureForCombat; import mage.filter.common.FilterCreatureForCombatBlock; import mage.filter.predicate.permanent.ControllerIdPredicate; import mage.game.Game; +import mage.game.combat.CombatGroup; import mage.game.draft.Draft; import mage.game.match.Match; import mage.game.permanent.Permanent; @@ -99,7 +100,7 @@ public class HumanPlayer extends PlayerImpl { protected static FilterAttackingCreature filterAttack = new FilterAttackingCreature(); protected static FilterBlockingCreature filterBlock = new FilterBlockingCreature(); protected static final Choice replacementEffectChoice = new ChoiceImpl(true); - private static final Map staticOptions = new HashMap(); + private static final Map staticOptions = new HashMap<>(); private static final Logger log = Logger.getLogger(HumanPlayer.class); @@ -692,10 +693,17 @@ public class HumanPlayer extends PlayerImpl { } else if (response.getUUID() != null) { Permanent blocker = game.getPermanent(response.getUUID()); if (blocker != null) { - if (filter.match(blocker, null, playerId, game)) { + boolean removeBlocker = false; + // does not block yet and can block or can block more attackers + if (filter.match(blocker, null, playerId, game)) { selectCombatGroup(defendingPlayerId, blocker.getId(), game); + } else { + if (filterBlock.match(blocker, null, playerId, game) && game.getStack().isEmpty()) { + removeBlocker = true; + } } - else if (filterBlock.match(blocker, null, playerId, game) && game.getStack().isEmpty()) { + + if (removeBlocker) { game.getCombat().removeBlocker(blocker.getId(), game); } } @@ -746,7 +754,15 @@ public class HumanPlayer extends PlayerImpl { if (response.getBoolean() != null) { // do nothing } else if (response.getUUID() != null) { - declareBlocker(defenderId, blockerId, response.getUUID(), game); + CombatGroup group = game.getCombat().findGroup(response.getUUID()); + if (group != null) { + // check if already blocked, if not add + if (!group.getBlockers().contains(blockerId)) { + declareBlocker(defenderId, blockerId, response.getUUID(), game); + } else { // else remove from block + game.getCombat().removeBlockerGromGroup(blockerId, group, game); + } + } } } diff --git a/Mage.Sets/src/mage/sets/riseoftheeldrazi/VirulentSwipe.java b/Mage.Sets/src/mage/sets/riseoftheeldrazi/VirulentSwipe.java index 7b86ad178b..2a2537a158 100644 --- a/Mage.Sets/src/mage/sets/riseoftheeldrazi/VirulentSwipe.java +++ b/Mage.Sets/src/mage/sets/riseoftheeldrazi/VirulentSwipe.java @@ -28,6 +28,7 @@ package mage.sets.riseoftheeldrazi; import java.util.UUID; +import mage.abilities.effects.Effect; import mage.constants.CardType; import mage.constants.Duration; import mage.constants.Rarity; @@ -51,9 +52,13 @@ public class VirulentSwipe extends CardImpl { this.color.setBlack(true); // Target creature gets +2/+0 and gains deathtouch until end of turn. - this.getSpellAbility().addTarget(new TargetCreaturePermanent()); - this.getSpellAbility().addEffect(new BoostTargetEffect(2, 0, Duration.EndOfTurn)); - this.getSpellAbility().addEffect(new GainAbilityTargetEffect(DeathtouchAbility.getInstance(), Duration.EndOfTurn)); + this.getSpellAbility().addTarget(new TargetCreaturePermanent(true)); + Effect effect = new BoostTargetEffect(2, 0, Duration.EndOfTurn); + effect.setText("Target creature gets +2/+0"); + this.getSpellAbility().addEffect(effect); + effect = new GainAbilityTargetEffect(DeathtouchAbility.getInstance(), Duration.EndOfTurn); + effect.setText("and gains deathtouch until end of turn"); + this.getSpellAbility().addEffect(effect); this.addAbility(new ReboundAbility()); } diff --git a/Mage/src/mage/abilities/effects/common/combat/CanBlockAdditionalCreatureEffect.java b/Mage/src/mage/abilities/effects/common/combat/CanBlockAdditionalCreatureEffect.java index d9b8c55c79..e1c1868ae4 100644 --- a/Mage/src/mage/abilities/effects/common/combat/CanBlockAdditionalCreatureEffect.java +++ b/Mage/src/mage/abilities/effects/common/combat/CanBlockAdditionalCreatureEffect.java @@ -53,7 +53,7 @@ public class CanBlockAdditionalCreatureEffect extends ContinuousEffectImpl 0) { - perm.setMaxBlocks(perm.getMaxBlocks() + amount); - } else { - perm.setMaxBlocks(0); - } - break; + Permanent permanent = game.getPermanent(source.getSourceId()); + if (permanent != null) { + // maxBlocks = 0 equals to "can block any number of creatures" + if (amount > 0) { + permanent.setMaxBlocks(permanent.getMaxBlocks() + amount); + } else { + permanent.setMaxBlocks(0); } return true; } @@ -105,11 +101,8 @@ public class CanBlockAdditionalCreatureEffect extends ContinuousEffectImpl { // There are effects that let creatures assigns combat damage equal to its toughness rather than its power private boolean useToughnessForDamage; - protected List groups = new ArrayList(); - protected Map blockingGroups = new HashMap(); - protected Set defenders = new HashSet(); + protected List groups = new ArrayList<>(); + protected Map blockingGroups = new HashMap<>(); + protected Set defenders = new HashSet<>(); // how many creatures attack defending player - protected Map> numberCreaturesDefenderAttackedBy = new HashMap>(); + protected Map> numberCreaturesDefenderAttackedBy = new HashMap<>(); protected UUID attackerId; //the player that is attacking // > - protected Map> creatureMustBlockAttackers = 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>(); + private final Map> creaturesForcedToAttack = new HashMap<>(); private int maxAttackers = Integer.MIN_VALUE; public Combat() { @@ -109,7 +109,7 @@ public class Combat implements Serializable, Copyable { } public List getAttackers() { - List attackers = new ArrayList(); + List attackers = new ArrayList<>(); for (CombatGroup group : groups) { attackers.addAll(group.attackers); } @@ -117,7 +117,7 @@ public class Combat implements Serializable, Copyable { } public List getBlockers() { - List blockers = new ArrayList(); + List blockers = new ArrayList<>(); for (CombatGroup group : groups) { blockers.addAll(group.blockers); } @@ -214,12 +214,12 @@ public class Combat implements Serializable, Copyable { //20101001 - 508.1d for (Permanent creature : player.getAvailableAttackers(game)) { boolean mustAttack = false; - Set defendersForcedToAttack = new HashSet(); - for (Map.Entry entry : game.getContinuousEffects().getApplicableRequirementEffects(creature, game).entrySet()) { - RequirementEffect effect = (RequirementEffect) entry.getKey(); + Set defendersForcedToAttack = new HashSet<>(); + for (Map.Entry> entry : game.getContinuousEffects().getApplicableRequirementEffects(creature, game).entrySet()) { + RequirementEffect effect = entry.getKey(); if (effect.mustAttack(game)) { mustAttack = true; - for (Ability ability : (HashSet) entry.getValue()) { + for (Ability ability : entry.getValue()) { UUID defenderId = effect.mustAttackDefender(ability, game); if (defenderId != null) { defendersForcedToAttack.add(defenderId); @@ -255,7 +255,7 @@ public class Combat implements Serializable, Copyable { } if (count > 1) { - List tobeRemoved = new ArrayList(); + List tobeRemoved = new ArrayList<>(); for (CombatGroup group : groups) { for (UUID attackingCreatureId : group.getAttackers()) { Permanent attacker = game.getPermanent(attackingCreatureId); @@ -272,7 +272,7 @@ public class Combat implements Serializable, Copyable { } if (count == 1) { - List tobeRemoved = new ArrayList(); + List tobeRemoved = new ArrayList<>(); for (CombatGroup group : groups) { for (UUID attackingCreatureId : group.getAttackers()) { Permanent attacker = game.getPermanent(attackingCreatureId); @@ -436,7 +436,7 @@ public class Combat implements Serializable, Copyable { if (creatureMustBlockAttackers.containsKey(possibleBlocker.getId())) { creatureMustBlockAttackers.get(possibleBlocker.getId()).add(attackingCreatureId); } else { - Set forcingAttackers = new HashSet(); + Set forcingAttackers = new HashSet<>(); forcingAttackers.add(attackingCreatureId); creatureMustBlockAttackers.put(possibleBlocker.getId(), forcingAttackers); // assign block to the first forcing attacker automatically @@ -479,7 +479,7 @@ public class Combat implements Serializable, Copyable { Set opponents = game.getOpponents(attackerId); //20101001 - 509.1c // map with attackers (UUID) that must be blocked by at least one blocker and a set of all creatures that can block it and don't block yet - Map> mustBeBlockedByAtLeastOne = new HashMap>(); + Map> mustBeBlockedByAtLeastOne = new HashMap<>(); // check mustBlock requirements of creatures from opponents of attacking player for (Permanent creature : game.getBattlefield().getActivePermanents(new FilterControlledCreaturePermanent(), player.getId(), game)) { @@ -489,17 +489,17 @@ public class Combat implements Serializable, Copyable { // Creature is already blocking but not forced to do so if (creature.getBlocking() > 0) { // get all requirement effects that apply to the creature (e.g. is able to block attacker) - for (Map.Entry entry : game.getContinuousEffects().getApplicableRequirementEffects(creature, game).entrySet()) { - RequirementEffect effect = (RequirementEffect) entry.getKey(); + for (Map.Entry> entry : game.getContinuousEffects().getApplicableRequirementEffects(creature, game).entrySet()) { + RequirementEffect effect = entry.getKey(); // get possible mustBeBlockedByAtLeastOne blocker - for (Ability ability : (HashSet) entry.getValue()) { + for (Ability ability : entry.getValue()) { UUID toBeBlockedCreature = effect.mustBlockAttackerIfElseUnblocked(ability, game); if (toBeBlockedCreature != null) { Set potentialBlockers; if (mustBeBlockedByAtLeastOne.containsKey(toBeBlockedCreature)) { potentialBlockers = mustBeBlockedByAtLeastOne.get(toBeBlockedCreature); } else { - potentialBlockers = new HashSet(); + potentialBlockers = new HashSet<>(); mustBeBlockedByAtLeastOne.put(toBeBlockedCreature, potentialBlockers); } potentialBlockers.add(creature.getId()); @@ -511,17 +511,17 @@ public class Combat implements Serializable, Copyable { // Creature is not blocking yet if (creature.getBlocking() == 0) { // get all requirement effects that apply to the creature - for (Map.Entry entry : game.getContinuousEffects().getApplicableRequirementEffects(creature, game).entrySet()) { - RequirementEffect effect = (RequirementEffect) entry.getKey(); + for (Map.Entry> entry : game.getContinuousEffects().getApplicableRequirementEffects(creature, game).entrySet()) { + RequirementEffect effect = entry.getKey(); // get possible mustBeBlockedByAtLeastOne blocker - for (Ability ability : (HashSet) entry.getValue()) { + for (Ability ability : entry.getValue()) { UUID toBeBlockedCreature = effect.mustBlockAttackerIfElseUnblocked(ability, game); if (toBeBlockedCreature != null) { Set potentialBlockers; if (mustBeBlockedByAtLeastOne.containsKey(toBeBlockedCreature)) { potentialBlockers = mustBeBlockedByAtLeastOne.get(toBeBlockedCreature); } else { - potentialBlockers = new HashSet(); + potentialBlockers = new HashSet<>(); mustBeBlockedByAtLeastOne.put(toBeBlockedCreature, potentialBlockers); } potentialBlockers.add(creature.getId()); @@ -701,9 +701,9 @@ public class Combat implements Serializable, Copyable { for (UUID attackingCreatureId : this.getAttackers()) { Permanent attackingCreature = game.getPermanent(attackingCreatureId); if (attackingCreature != null) { - for (Map.Entry entry : game.getContinuousEffects().getApplicableRestrictionEffects(attackingCreature, game).entrySet()) { - RestrictionEffect effect = (RestrictionEffect) entry.getKey(); - for (Ability ability : (HashSet) entry.getValue()) { + for (Map.Entry> entry : game.getContinuousEffects().getApplicableRestrictionEffects(attackingCreature, game).entrySet()) { + RestrictionEffect effect = entry.getKey(); + for (Ability ability : 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()); @@ -818,7 +818,7 @@ public class Combat implements Serializable, Copyable { if (numberCreaturesDefenderAttackedBy.containsKey(defendingPlayer.getId())) { defenderAttackedBy = numberCreaturesDefenderAttackedBy.get(defendingPlayer.getId()); } else { - defenderAttackedBy = new HashSet(); + defenderAttackedBy = new HashSet<>(); numberCreaturesDefenderAttackedBy.put(defendingPlayer.getId(), defenderAttackedBy); } if (defenderAttackedBy.size() >= defendingPlayer.getMaxAttackedBy()) { @@ -980,7 +980,7 @@ public class Combat implements Serializable, Copyable { } public Set getPlayerDefenders(Game game) { - Set playerDefenders = new HashSet(); + Set playerDefenders = new HashSet<>(); for (CombatGroup group : groups) { if (group.defenderIsPlaneswalker) { Permanent permanent = game.getPermanent(group.getDefenderId()); @@ -1027,6 +1027,27 @@ public class Combat implements Serializable, Copyable { } } + + public void removeBlockerGromGroup(UUID blockerId, CombatGroup groupToUnblock, Game game) { + Permanent creature = game.getPermanent(blockerId); + if (creature != null) { + for (CombatGroup group : groups) { + if (group.equals(groupToUnblock) && group.blockers.contains(blockerId)) { + group.blockers.remove(blockerId); + group.blockerOrder.remove(blockerId); + if (group.blockers.isEmpty()) { + group.blocked = false; + } + if (creature.getBlocking() > 0) { + creature.setBlocking(creature.getBlocking() - 1); + } else { + throw new UnsupportedOperationException("Tryinging creature to unblock, but blocking number value of creature < 1"); + } + } + } + } + } + public void removeBlocker(UUID blockerId, Game game) { for (CombatGroup group : groups) { if (group.blockers.contains(blockerId)) {