diff --git a/Mage/src/mage/abilities/common/GainLifeControllerTriggeredAbility.java b/Mage/src/mage/abilities/common/GainLifeControllerTriggeredAbility.java index f280c3cad1..44632666c8 100644 --- a/Mage/src/mage/abilities/common/GainLifeControllerTriggeredAbility.java +++ b/Mage/src/mage/abilities/common/GainLifeControllerTriggeredAbility.java @@ -33,6 +33,7 @@ import mage.abilities.effects.Effect; import mage.constants.Zone; import mage.game.Game; import mage.game.events.GameEvent; +import mage.target.targetpointer.FixedTarget; /** * @@ -40,13 +41,20 @@ import mage.game.events.GameEvent; */ public class GainLifeControllerTriggeredAbility extends TriggeredAbilityImpl<GainLifeControllerTriggeredAbility> { + private boolean setTargetPointer; public GainLifeControllerTriggeredAbility(Effect effect, boolean optional) { + this(effect, optional, false); + } + + public GainLifeControllerTriggeredAbility(Effect effect, boolean optional, boolean setTargetPointer) { super(Zone.BATTLEFIELD, effect, optional); + this.setTargetPointer = setTargetPointer; } public GainLifeControllerTriggeredAbility(final GainLifeControllerTriggeredAbility ability) { super(ability); + this.setTargetPointer = ability.setTargetPointer; } @Override @@ -56,7 +64,16 @@ public class GainLifeControllerTriggeredAbility extends TriggeredAbilityImpl<Gai @Override public boolean checkTrigger(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.GAINED_LIFE && event.getPlayerId().equals(this.getControllerId()); + if (event.getType() == GameEvent.EventType.GAINED_LIFE && event.getPlayerId().equals(this.getControllerId())) { + if (setTargetPointer) { + for (Effect effect : this.getEffects()) { + effect.setTargetPointer(new FixedTarget(event.getPlayerId())); + effect.setValue("gainedLife", event.getAmount()); + } + return true; + } + } + return false; } @Override diff --git a/Mage/src/mage/abilities/effects/common/continious/GainControlTargetEffect.java b/Mage/src/mage/abilities/effects/common/continious/GainControlTargetEffect.java index d8505ade03..4680330041 100644 --- a/Mage/src/mage/abilities/effects/common/continious/GainControlTargetEffect.java +++ b/Mage/src/mage/abilities/effects/common/continious/GainControlTargetEffect.java @@ -45,22 +45,41 @@ import mage.game.permanent.Permanent; */ public class GainControlTargetEffect extends ContinuousEffectImpl<GainControlTargetEffect> { - private UUID fixedControllerId; - private boolean fixedController; + private UUID controllingPlayerId; + private boolean fixedControl; public GainControlTargetEffect(Duration duration) { - this(duration, false); + this(duration, false, null); } - public GainControlTargetEffect(Duration duration, boolean fixedController) { + /** + * + * @param duration + * @param fixedControl Controlling player is fixed even if the controller of the ability chnages + */ + public GainControlTargetEffect(Duration duration, boolean fixedControl) { + this(duration, fixedControl, null); + } + + /** + * + * @param duration + * @param controllingPlayerId Player that controlls the target creature + */ + public GainControlTargetEffect(Duration duration, UUID controllingPlayerId) { + this(duration, true, controllingPlayerId); + + } + public GainControlTargetEffect(Duration duration, boolean fixedControl, UUID controllingPlayerId) { super(duration, Layer.ControlChangingEffects_2, SubLayer.NA, Outcome.GainControl); - this.fixedController = fixedController; + this.controllingPlayerId = controllingPlayerId; + this.fixedControl = fixedControl; } public GainControlTargetEffect(final GainControlTargetEffect effect) { super(effect); - this.fixedControllerId = effect.fixedControllerId; - this.fixedController = effect.fixedController; + this.controllingPlayerId = effect.controllingPlayerId; + this.fixedControl = effect.fixedControl; } @Override @@ -70,8 +89,8 @@ public class GainControlTargetEffect extends ContinuousEffectImpl<GainControlTar @Override public void init(Ability source, Game game) { - if (fixedController) { - this.fixedControllerId = source.getControllerId(); + if (this.controllingPlayerId == null && fixedControl) { + this.controllingPlayerId = source.getControllerId(); } } @@ -79,18 +98,21 @@ public class GainControlTargetEffect extends ContinuousEffectImpl<GainControlTar public boolean apply(Game game, Ability source) { Permanent permanent = game.getPermanent(targetPointer.getFirst(game, source)); if (permanent != null) { - if (fixedController) { - return permanent.changeControllerId(fixedControllerId, game); + if (controllingPlayerId != null) { + return permanent.changeControllerId(controllingPlayerId, game); } else { return permanent.changeControllerId(source.getControllerId(), game); } - } + this.discard(); return false; } @Override public String getText(Mode mode) { + if (!staticText.isEmpty()) { + return staticText; + } return "Gain control of target " + mode.getTargets().get(0).getTargetName() + " " + duration.toString(); } } diff --git a/Mage/src/mage/game/combat/Combat.java b/Mage/src/mage/game/combat/Combat.java index 9703399b5b..afa94cb982 100644 --- a/Mage/src/mage/game/combat/Combat.java +++ b/Mage/src/mage/game/combat/Combat.java @@ -53,6 +53,7 @@ import mage.game.permanent.Permanent; import mage.players.Player; import mage.players.PlayerList; import mage.target.common.TargetDefender; +import mage.util.CardUtil; import mage.util.Copyable; import mage.util.trace.TraceUtil; @@ -70,10 +71,15 @@ public class Combat implements Serializable, Copyable<Combat> { protected List<CombatGroup> groups = new ArrayList<CombatGroup>(); protected Map<UUID, CombatGroup> blockingGroups = new HashMap<UUID, CombatGroup>(); protected Set<UUID> defenders = new HashSet<UUID>(); + // how many creatures attack defending player + protected Map<UUID, Set<UUID>> numberCreaturesDefenderAttackedBy = new HashMap<UUID, Set<UUID>>(); protected UUID attackerId; //the player that is attacking // <creature that can block, <all attackers that force the creature to block it>> protected Map<UUID, Set<UUID>> creaturesForcedToBlockAttackers = new HashMap<UUID, Set<UUID>>(); + // which creature is forced to attack which defender(s). If set is empty, the creature can attack every possible defender + private final Map<UUID, Set<UUID>> creaturesForcedToAttack = new HashMap<UUID, Set<UUID>>(); + private int maxAttackers = Integer.MIN_VALUE; public Combat() { @@ -138,6 +144,9 @@ public class Combat implements Serializable, Copyable<Combat> { defenders.clear(); attackerId = null; creaturesForcedToBlockAttackers.clear(); + numberCreaturesDefenderAttackedBy.clear(); + creaturesForcedToAttack.clear(); + maxAttackers = Integer.MIN_VALUE; } public String getValue() { @@ -207,27 +216,38 @@ public class Combat implements Serializable, Copyable<Combat> { protected void checkAttackRequirements(Player player, Game game) { //20101001 - 508.1d for (Permanent creature : player.getAvailableAttackers(game)) { + boolean mustAttack = false; + Set <UUID> defendersForcedToAttack = new HashSet<UUID>(); for (Map.Entry entry : game.getContinuousEffects().getApplicableRequirementEffects(creature, game).entrySet()) { RequirementEffect effect = (RequirementEffect)entry.getKey(); if (effect.mustAttack(game)) { + mustAttack = true; for (Ability ability: (HashSet<Ability>)entry.getValue()) { UUID defenderId = effect.mustAttackDefender(ability, game); - if (defenderId == null) { - if (defenders.size() == 1) { - player.declareAttacker(creature.getId(), defenders.iterator().next(), game); - } else { - TargetDefender target = new TargetDefender(defenders, creature.getId()); - target.setRequired(true); - if (player.chooseTarget(Outcome.Damage, target, null, game)) { - player.declareAttacker(creature.getId(), target.getFirstTarget(), game); - } - } - } else { - player.declareAttacker(creature.getId(), defenderId, game); + if(defenderId != null) { + defendersForcedToAttack.add(defenderId); } + break; } } } + if (mustAttack) { + creaturesForcedToAttack.put(creature.getId(),defendersForcedToAttack); + if (defendersForcedToAttack.isEmpty()) { + if (defenders.size() == 1) { + player.declareAttacker(creature.getId(), defenders.iterator().next(), game); + } else { + TargetDefender target = new TargetDefender(defenders, creature.getId()); + target.setRequired(true); + if (player.chooseTarget(Outcome.Damage, target, null, game)) { + player.declareAttacker(creature.getId(), target.getFirstTarget(), game); + } + } + } else { + player.declareAttacker(creature.getId(), defendersForcedToAttack.iterator().next(), game); + } + } + } } @@ -300,35 +320,42 @@ public class Combat implements Serializable, Copyable<Combat> { game.fireEvent(GameEvent.getEvent(GameEvent.EventType.DECLARED_BLOCKERS, defenderId, defenderId)); // add info about attacker blocked by blocker to the game log + boolean shownDefendingPlayer = false; for (CombatGroup group :this.getGroups()) { - StringBuilder sb = new StringBuilder(); - for(UUID attackingCreatureId : group.getAttackers()) { - Permanent attackingCreature = game.getPermanent(attackingCreatureId); - if (attackingCreature != null) { - sb.append(attackingCreature.getName()).append(" ("); - sb.append(attackingCreature.getPower().getValue()).append("/").append(attackingCreature.getToughness().getValue()).append(") "); - } else { - // creature left battlefield - attackingCreature = (Permanent) game.getLastKnownInformation(attackingCreatureId, Zone.BATTLEFIELD); + if (group.defendingPlayerId.equals(defenderId)) { + if (!shownDefendingPlayer && defender != null) { + game.informPlayers(new StringBuilder("Attacked player: ").append(defender.getName()).toString()); + shownDefendingPlayer = true; + } + StringBuilder sb = new StringBuilder(); + for(UUID attackingCreatureId : group.getAttackers()) { + Permanent attackingCreature = game.getPermanent(attackingCreatureId); if (attackingCreature != null) { - sb.append(attackingCreature.getName()).append(" [left battlefield)] "); + sb.append(attackingCreature.getName()).append(" ("); + sb.append(attackingCreature.getPower().getValue()).append("/").append(attackingCreature.getToughness().getValue()).append(") "); + } else { + // creature left battlefield + attackingCreature = (Permanent) game.getLastKnownInformation(attackingCreatureId, Zone.BATTLEFIELD); + if (attackingCreature != null) { + sb.append(attackingCreature.getName()).append(" [left battlefield)] "); + } } } - } - if (group.getBlockers().size() > 0) { - sb.append("blocked by "); - for(UUID blockingCreatureId : group.getBlockers()) { - Permanent blockingCreature = game.getPermanent(blockingCreatureId); - if (blockingCreature != null) { - sb.append(blockingCreature.getName()).append(" ("); - sb.append(blockingCreature.getPower().getValue()).append("/").append(blockingCreature.getToughness().getValue()).append(") "); + if (group.getBlockers().size() > 0) { + sb.append("blocked by "); + for(UUID blockingCreatureId : group.getBlockers()) { + Permanent blockingCreature = game.getPermanent(blockingCreatureId); + if (blockingCreature != null) { + sb.append(blockingCreature.getName()).append(" ("); + sb.append(blockingCreature.getPower().getValue()).append("/").append(blockingCreature.getToughness().getValue()).append(") "); + } } - } - } else{ - sb.append("unblocked"); + } else{ + sb.append("unblocked"); + } + game.informPlayers(sb.toString()); } - game.informPlayers(sb.toString()); } } @@ -647,17 +674,38 @@ public class Combat implements Serializable, Copyable<Combat> { } private void addDefender(UUID defenderId, Game game) { - defenders.add(defenderId); - for (Permanent permanent : game.getBattlefield().getAllActivePermanents(filterPlaneswalker, defenderId, game)) { - defenders.add(permanent.getId()); + if (!defenders.contains(defenderId)) { + if (maxAttackers < Integer.MAX_VALUE) { + Player defendingPlayer = game.getPlayer(defenderId); + if (defendingPlayer != null) { + if (defendingPlayer.getMaxAttackedBy() == Integer.MAX_VALUE) { + maxAttackers = Integer.MAX_VALUE; + } else { + if (maxAttackers == Integer.MIN_VALUE) { + maxAttackers = defendingPlayer.getMaxAttackedBy(); + } else { + maxAttackers += defendingPlayer.getMaxAttackedBy(); + } + } + } + } + defenders.add(defenderId); + for (Permanent permanent : game.getBattlefield().getAllActivePermanents(filterPlaneswalker, defenderId, game)) { + defenders.add(permanent.getId()); + } } } - public void declareAttacker(UUID attackerId, UUID defenderId, Game game) { + public boolean declareAttacker(UUID attackerId, UUID defenderId, Game game) { if (!defenders.contains(defenderId)) { - return; + return false; } Permanent defender = game.getPermanent(defenderId); + // Check if defending player can be attacked with another creature + if (!canDefenderBeAttacked(attackerId, defenderId, game)) { + return false; + } + CombatGroup newGroup = new CombatGroup(defenderId, defender != null, defender != null ? defender.getControllerId(): defenderId); newGroup.attackers.add(attackerId); Permanent attacker = game.getPermanent(attackerId); @@ -666,6 +714,39 @@ public class Combat implements Serializable, Copyable<Combat> { } attacker.setAttacking(true); groups.add(newGroup); + return true; + } + + public boolean canDefenderBeAttacked(UUID attackerId, UUID defenderId, Game game) { + Permanent defender = game.getPermanent(defenderId); + // Check if defending player can be attacked with another creature + if (defender != null) { + // a planeswalker is attacked, there exits no restriction yet for attacking planeswalker + return true; + } + Player defendingPlayer = game.getPlayer(defenderId); + if (defendingPlayer == null) { + return false; + } + Set<UUID> defenderAttackedBy; + if (numberCreaturesDefenderAttackedBy.containsKey(defendingPlayer.getId())) { + defenderAttackedBy = numberCreaturesDefenderAttackedBy.get(defendingPlayer.getId()); + } else { + defenderAttackedBy = new HashSet<UUID>(); + numberCreaturesDefenderAttackedBy.put(defendingPlayer.getId(), defenderAttackedBy); + } + if (defenderAttackedBy.size() >= defendingPlayer.getMaxAttackedBy()) { + Player attackingPlayer = game.getPlayer(game.getControllerId(attackerId)); + if (attackingPlayer != null) { + game.informPlayer(attackingPlayer, new StringBuilder("No more than ") + .append(CardUtil.numberToText(defendingPlayer.getMaxAttackedBy())) + .append(" creatures can attack ") + .append(defendingPlayer.getName()).toString()); + } + return false; + } + defenderAttackedBy.add(attackerId); + return true; } // add blocking group for creatures that block more than one creature @@ -757,10 +838,7 @@ public class Combat implements Serializable, Copyable<Combat> { } public boolean noAttackers() { - if (groups.isEmpty() || getAttackers().isEmpty()) { - return true; - } - return false; + return groups.isEmpty() || getAttackers().isEmpty(); } public boolean isAttacked(UUID defenderId, Game game) { @@ -845,6 +923,9 @@ public class Combat implements Serializable, Copyable<Combat> { if (group.attackers.contains(attackerId)) { group.attackers.remove(attackerId); group.attackerOrder.remove(attackerId); + for (Set<UUID> attackingCreatures : numberCreaturesDefenderAttackedBy.values()) { + attackingCreatures.remove(attackerId); + } Permanent creature = game.getPermanent(attackerId); if (creature != null) { creature.setAttacking(false); @@ -878,6 +959,15 @@ public class Combat implements Serializable, Copyable<Combat> { return attackerId; } + public Map<UUID, Set<UUID>> getCreaturesForcedToAttack() { + return creaturesForcedToAttack; + } + + public int getMaxAttackers() { + return maxAttackers; + } + + @Override public Combat copy() { return new Combat(this); diff --git a/Mage/src/mage/players/Player.java b/Mage/src/mage/players/Player.java index 8de3200beb..75513deb61 100644 --- a/Mage/src/mage/players/Player.java +++ b/Mage/src/mage/players/Player.java @@ -102,6 +102,8 @@ public interface Player extends MageItem, Copyable<Player> { void setLandsPerTurn(int landsPerTurn); int getMaxHandSize(); void setMaxHandSize(int maxHandSize); + int getMaxAttackedBy(); + void setMaxAttackedBy(int maxAttackedBy); boolean isPassed(); boolean isEmptyDraw(); void pass(Game game); diff --git a/Mage/src/mage/players/PlayerImpl.java b/Mage/src/mage/players/PlayerImpl.java index f1b010a418..b74f08d073 100644 --- a/Mage/src/mage/players/PlayerImpl.java +++ b/Mage/src/mage/players/PlayerImpl.java @@ -139,6 +139,7 @@ public abstract class PlayerImpl<T extends PlayerImpl<T>> implements Player, Ser protected int landsPlayed; protected int landsPerTurn = 1; protected int maxHandSize = 7; + protected int maxAttackedBy = Integer.MAX_VALUE; protected ManaPool manaPool; protected boolean passed; protected boolean passedTurn; @@ -219,6 +220,7 @@ public abstract class PlayerImpl<T extends PlayerImpl<T>> implements Player, Ser this.landsPlayed = player.landsPlayed; this.landsPerTurn = player.landsPerTurn; this.maxHandSize = player.maxHandSize; + this.maxAttackedBy = player.maxAttackedBy; this.manaPool = player.manaPool.copy(); this.turns = player.turns; @@ -269,6 +271,7 @@ public abstract class PlayerImpl<T extends PlayerImpl<T>> implements Player, Ser this.landsPlayed = player.getLandsPlayed(); this.landsPerTurn = player.getLandsPerTurn(); this.maxHandSize = player.getMaxHandSize(); + this.maxAttackedBy = player.getMaxAttackedBy(); this.manaPool = player.getManaPool().copy(); this.turns = player.getTurns(); @@ -345,6 +348,7 @@ public abstract class PlayerImpl<T extends PlayerImpl<T>> implements Player, Ser this.abilities.clear(); this.landsPerTurn = 1; this.maxHandSize = 7; + this.maxAttackedBy = Integer.MAX_VALUE; this.canGainLife = true; this.canLoseLife = true; this.canPayLifeCost = true; @@ -1386,6 +1390,16 @@ public abstract class PlayerImpl<T extends PlayerImpl<T>> implements Player, Ser this.maxHandSize = maxHandSize; } + @Override + public void setMaxAttackedBy(int maxAttackedBy) { + this.maxAttackedBy = maxAttackedBy; + } + + @Override + public int getMaxAttackedBy() { + return maxAttackedBy; + } + @Override public void setResponseString(String responseString) {} @@ -1604,6 +1618,8 @@ public abstract class PlayerImpl<T extends PlayerImpl<T>> implements Player, Ser } /** + * @param game + * @param appliedEffects * * @return true if player won the toss */ diff --git a/Mage/src/mage/target/Target.java b/Mage/src/mage/target/Target.java index b5b7ea92de..25061549ec 100644 --- a/Mage/src/mage/target/Target.java +++ b/Mage/src/mage/target/Target.java @@ -50,9 +50,10 @@ public interface Target extends Serializable { void clearChosen(); boolean isNotTarget(); - // controlls if it will be checked, if the target can be targeted from source - // true = check for protection - // false = do not check for protection + /** + * controlls if it will be checked, if the target can be targeted from source + * @param notTarget true = check for protection, false = do not check for protection + */ void setNotTarget(boolean notTarget); // methods for targets