Package mage.game.combat

Source Code of mage.game.combat.Combat

/*
* 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.game.combat;

import mage.abilities.Ability;
import mage.abilities.effects.RequirementEffect;
import mage.abilities.effects.RestrictionEffect;
import mage.abilities.keyword.CanAttackOnlyAloneAbility;
import mage.abilities.keyword.CantAttackAloneAbility;
import mage.abilities.keyword.VigilanceAbility;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.filter.common.FilterControlledCreaturePermanent;
import mage.filter.common.FilterCreatureForCombatBlock;
import mage.filter.common.FilterPlaneswalkerPermanent;
import mage.game.Game;
import mage.game.events.GameEvent;
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;

import java.io.Serializable;
import java.util.*;

/**
* @author BetaSteward_at_googlemail.com
*/
public class Combat implements Serializable, Copyable<Combat> {

    private static FilterPlaneswalkerPermanent filterPlaneswalker = new FilterPlaneswalkerPermanent();
    private static FilterCreatureForCombatBlock filterBlockers = new FilterCreatureForCombatBlock();
    // There are effects that let creatures assigns combat damage equal to its toughness rather than its power
    private boolean useToughnessForDamage;

    protected List<CombatGroup> groups = new ArrayList<>();
    protected Map<UUID, CombatGroup> blockingGroups = new HashMap<>();
    protected Set<UUID> defenders = new HashSet<>();
    // how many creatures attack defending player
    protected Map<UUID, Set<UUID>> numberCreaturesDefenderAttackedBy = new HashMap<>();
    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>> 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<UUID, Set<UUID>> creaturesForcedToAttack = new HashMap<>();
    private int maxAttackers = Integer.MIN_VALUE;

    public Combat() {
        this.useToughnessForDamage = false;
    }

    public Combat(final Combat combat) {
        this.attackerId = combat.attackerId;
        for (CombatGroup group : combat.groups) {
            groups.add(group.copy());
        }
        defenders.addAll(combat.defenders);
        for (Map.Entry<UUID, CombatGroup> group : combat.blockingGroups.entrySet()) {
            blockingGroups.put(group.getKey(), group.getValue());
        }
        this.useToughnessForDamage = combat.useToughnessForDamage;
        for (Map.Entry<UUID, Set<UUID>> group : combat.numberCreaturesDefenderAttackedBy.entrySet()) {
            this.numberCreaturesDefenderAttackedBy.put(group.getKey(), group.getValue());
        }       
       
        for (Map.Entry<UUID, Set<UUID>> group : combat.creatureMustBlockAttackers.entrySet()) {
            this.creatureMustBlockAttackers.put(group.getKey(), group.getValue());
        }
        for (Map.Entry<UUID, Set<UUID>> group : combat.creaturesForcedToAttack.entrySet()) {
            this.creaturesForcedToAttack.put(group.getKey(), group.getValue());
        }
        this.maxAttackers = combat.maxAttackers;
    }

    public List<CombatGroup> getGroups() {
        return groups;
    }

    public Collection<CombatGroup> getBlockingGroups() {
        return blockingGroups.values();
    }

    public Set<UUID> getDefenders() {
        return defenders;
    }

    public List<UUID> getAttackers() {
        List<UUID> attackers = new ArrayList<>();
        for (CombatGroup group : groups) {
            attackers.addAll(group.attackers);
        }
        return attackers;
    }

    public List<UUID> getBlockers() {
        List<UUID> blockers = new ArrayList<>();
        for (CombatGroup group : groups) {
            blockers.addAll(group.blockers);
        }
        return blockers;
    }

    public boolean useToughnessForDamage() {
        return useToughnessForDamage;
    }

    public void setUseToughnessForDamage(boolean useToughnessForDamage) {
        this.useToughnessForDamage = useToughnessForDamage;
    }

    public void reset() {
        this.useToughnessForDamage = false;
    }

    public void clear() {
        groups.clear();
        blockingGroups.clear();
        defenders.clear();
        attackerId = null;
        creatureMustBlockAttackers.clear();
        numberCreaturesDefenderAttackedBy.clear();
        creaturesForcedToAttack.clear();
        maxAttackers = Integer.MIN_VALUE;
    }

    public String getValue() {
        StringBuilder sb = new StringBuilder();
        sb.append(attackerId).append(defenders);
        for (CombatGroup group : groups) {
            sb.append(group.defenderId).append(group.attackers).append(group.attackerOrder).append(group.blockers).append(group.blockerOrder);
        }
        return sb.toString();
    }

    public void setAttacker(UUID playerId) {
        this.attackerId = playerId;
    }

    /**
     * Add an additional attacker to the combat (e.g. token of Geist of Saint
     * Traft) This method doesn't trigger ATTACKER_DECLARED event (as intended).
     *
     * @param creatureId - creature that shall be added to the combat
     * @param game
     * @return
     */
    public boolean addAttackingCreature(UUID creatureId, Game game) {
        Player player = game.getPlayer(attackerId);
        if (defenders.size() == 1) {
            declareAttacker(creatureId, defenders.iterator().next(), game);
            return true;
        } else {
            TargetDefender target = new TargetDefender(defenders, creatureId);
            target.setRequired(true);
            player.chooseTarget(Outcome.Damage, target, null, game);
            if (target.getFirstTarget() != null) {
                declareAttacker(creatureId, target.getFirstTarget(), game);
                return true;
            }
        }
        return false;
    }

    public void selectAttackers(Game game) {
        if (!game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.DECLARING_ATTACKERS, attackerId, attackerId))) {
            Player player = game.getPlayer(attackerId);
            //20101001 - 508.1d
            checkAttackRequirements(player, game);
            if (!game.getPlayer(game.getActivePlayerId()).getAvailableAttackers(game).isEmpty()) {
                player.selectAttackers(game, attackerId);
            }
            if (game.isPaused() || game.gameOver(null)) {
                return;
            }
            checkAttackRestrictions(player, game);
            resumeSelectAttackers(game);
        }
    }

    public void resumeSelectAttackers(Game game) {
        Player player = game.getPlayer(attackerId);
        for (CombatGroup group : groups) {
            for (UUID attacker : group.getAttackers()) {
                game.fireEvent(GameEvent.getEvent(GameEvent.EventType.ATTACKER_DECLARED, group.defenderId, attacker, attackerId));
            }
        }
        game.fireEvent(GameEvent.getEvent(GameEvent.EventType.DECLARED_ATTACKERS, attackerId, attackerId));
        game.informPlayers(new StringBuilder(player.getName()).append(" attacks with ").append(groups.size()).append(groups.size() == 1 ? " creature":" creatures").toString());
    }

    protected void checkAttackRequirements(Player player, Game game) {
        //20101001 - 508.1d
        for (Permanent creature : player.getAvailableAttackers(game)) {
            boolean mustAttack = false;
            Set<UUID> defendersForcedToAttack = new HashSet<>();
            for (Map.Entry<RequirementEffect, HashSet<Ability>> entry : game.getContinuousEffects().getApplicableRequirementEffects(creature, game).entrySet()) {
                RequirementEffect effect = entry.getKey();
                if (effect.mustAttack(game)) {
                    mustAttack = true;
                    for (Ability ability : entry.getValue()) {
                        UUID defenderId = effect.mustAttackDefender(ability, 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, false);
                    } 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, false);
                        }
                    }
                } else {
                    player.declareAttacker(creature.getId(), defendersForcedToAttack.iterator().next(), game, false);
                }
            }

        }
    }

    protected void checkAttackRestrictions(Player player, Game game) {
        int count = 0;
        for (CombatGroup group : groups) {
            count += group.getAttackers().size();
        }

        if (count > 1) {
            List<UUID> tobeRemoved = new ArrayList<>();
            for (CombatGroup group : groups) {
                for (UUID attackingCreatureId : group.getAttackers()) {
                    Permanent attacker = game.getPermanent(attackingCreatureId);
                    if (count > 1 && attacker != null && attacker.getAbilities().containsKey(CanAttackOnlyAloneAbility.getInstance().getId())) {
                        game.informPlayers(attacker.getLogName() + " can only attack alone. Removing it from combat.");
                        tobeRemoved.add(attackingCreatureId);
                        count--;
                    }
                }
            }
            for (UUID attackingCreatureId : tobeRemoved) {
                this.removeAttacker(attackingCreatureId, game);
            }
        }

        if (count == 1) {
            List<UUID> tobeRemoved = new ArrayList<>();
            for (CombatGroup group : groups) {
                for (UUID attackingCreatureId : group.getAttackers()) {
                    Permanent attacker = game.getPermanent(attackingCreatureId);
                    if (attacker != null && attacker.getAbilities().containsKey(CantAttackAloneAbility.getInstance().getId())) {
                        game.informPlayers(attacker.getLogName() + " can't attack alone. Removing it from combat.");
                        tobeRemoved.add(attackingCreatureId);
                    }
                }
            }
            for (UUID attackingCreatureId : tobeRemoved) {
                this.removeAttacker(attackingCreatureId, game);
            }

        }

    }

    public void selectBlockers(Game game) {
        if (!game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.DECLARING_BLOCKERS, attackerId, attackerId))) {
            game.getCombat().selectBlockers(null, game);
        }
    }

    /**
     * Handle the blocker selection process
     *
     * @param blockController player that controlls how to block, if null the
     * defender is the controller
     * @param game
     */
    public void selectBlockers(Player blockController, Game game) {
        Player attacker = game.getPlayer(attackerId);
        //20101001 - 509.1c
        this.retrieveMustBlockAttackerRequirements(attacker, game);
        for (UUID defenderId : getPlayerDefenders(game)) {
            Player defender = game.getPlayer(defenderId);
            if (defender != null) {
                boolean choose = true;
                if (blockController == null) {
                    blockController = defender;
                }
                while (choose) {
                    blockController.selectBlockers(game, defenderId);
                    if (game.isPaused() || game.gameOver(null)) {
                        return;
                    }
                    if (!this.checkBlockRestrictions(defender, game)) {
                        if (blockController.isHuman()) { // only human player can decide to do the block in another way
                            continue;
                        }
                    }
                    choose = !this.checkBlockRequirementsAfter(defender, blockController, game);
                    if (!choose) {
                        choose = !this.checkBlockRestrictionsAfter(defender, blockController, game);
                    }
                }
                game.fireEvent(GameEvent.getEvent(GameEvent.EventType.DECLARED_BLOCKERS, defenderId, defenderId));

                // add info about attacker blocked by blocker to the game log
                this.logBlockerInfo(defender, game);
            }
        }
        // tool to catch the bug about flyers blocked by non flyers or intimidate blocked by creatures with other colors
        TraceUtil.traceCombatIfNeeded(game, this);
    }

    /**
     * Add info about attacker blocked by blocker to the game log
     *
     */
    private void logBlockerInfo(Player defender, Game game) {
        boolean shownDefendingPlayer = game.getPlayers().size() < 3; // only two players no ned to sow the attacked player
        for (CombatGroup group : this.getGroups()) {
            if (group.defendingPlayerId.equals(defender.getId())) {
                if (!shownDefendingPlayer) {
                    game.informPlayers(new StringBuilder("Attacked player: ").append(defender.getName()).toString());
                    shownDefendingPlayer = true;
                }
                StringBuilder sb = new StringBuilder();
                boolean attackerExists = false;
                for (UUID attackingCreatureId : group.getAttackers()) {
                    attackerExists = true;
                    Permanent attackingCreature = game.getPermanent(attackingCreatureId);
                    if (attackingCreature != null) {
                        sb.append("Attacker: ");
                        sb.append(attackingCreature.getLogName()).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.getLogName()).append(" [left battlefield)] ");
                        }
                    }
                }
                if (attackerExists) {
                    if (group.getBlockers().size() > 0) {
                        sb.append("blocked by ");
                        for (UUID blockingCreatureId : group.getBlockerOrder()) {
                            Permanent blockingCreature = game.getPermanent(blockingCreatureId);
                            if (blockingCreature != null) {
                                sb.append(blockingCreature.getLogName()).append(" (");
                                sb.append(blockingCreature.getPower().getValue()).append("/").append(blockingCreature.getToughness().getValue()).append(") ");
                            }
                        }

                    } else {
                        sb.append("unblocked");
                    }
                }
                game.informPlayers(sb.toString());
            }
        }
    }

    /**
     * Check the block restrictions
     *
     * @param player
     * @param game
     * @return false - if block restrictions were not complied
     */
    public boolean checkBlockRestrictions(Player player, Game game) {
        int count = 0;
        boolean blockWasLegal = true;
        for (CombatGroup group : groups) {
            count += group.getBlockers().size();
        }
        for (CombatGroup group : groups) {
            blockWasLegal &= group.checkBlockRestrictions(game, count);
        }
        return blockWasLegal;
    }

    public void acceptBlockers(Game game) {
        for (CombatGroup group : groups) {
            group.acceptBlockers(game);
        }
    }

    public void resumeSelectBlockers(Game game) {
        //TODO: this isn't quite right - but will work fine for two-player games
        for (UUID defenderId : getPlayerDefenders(game)) {
            game.fireEvent(GameEvent.getEvent(GameEvent.EventType.DECLARED_BLOCKERS, defenderId, defenderId));
        }
    }

    /**
     * Retrieves all requirements that apply and creates a Map with blockers and attackers
     * // Map<creature that can block, Set< all attackers the creature can block and force it to block the attacker>>
     *
     * @param attackingPlayer - 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<RequirementEffect, HashSet<Ability>> 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<UUID> 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);
                            }
                        }
                    }
                }
            }
        }
    }

    /**
     * 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<UUID> 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<UUID, Set<UUID>> mustBeBlockedByAtLeastOne = new HashMap<>();

        // check mustBlock requirements of creatures from opponents of attacking player
        for (Permanent creature : game.getBattlefield().getActivePermanents(new FilterControlledCreaturePermanent(), player.getId(), game)) {
            // creature is controlled by an opponent of the attacker
            if (opponents.contains(creature.getControllerId())) {

                // 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<RequirementEffect, HashSet<Ability>> entry : game.getContinuousEffects().getApplicableRequirementEffects(creature, game).entrySet()) {
                        RequirementEffect effect = entry.getKey();
                        // get possible mustBeBlockedByAtLeastOne blocker
                        for (Ability ability : entry.getValue()) {
                            UUID toBeBlockedCreature = effect.mustBlockAttackerIfElseUnblocked(ability, game);
                            if (toBeBlockedCreature != null) {
                                Set<UUID> potentialBlockers;
                                if (mustBeBlockedByAtLeastOne.containsKey(toBeBlockedCreature)) {
                                    potentialBlockers = mustBeBlockedByAtLeastOne.get(toBeBlockedCreature);
                                } else {
                                    potentialBlockers = new HashSet<>();
                                    mustBeBlockedByAtLeastOne.put(toBeBlockedCreature, potentialBlockers);
                                }
                                potentialBlockers.add(creature.getId());
                            }
                        }
                    }
                }

                // Creature is not blocking yet
                if (creature.getBlocking() == 0) {
                    // get all requirement effects that apply to the creature
                    for (Map.Entry<RequirementEffect, HashSet<Ability>> entry : game.getContinuousEffects().getApplicableRequirementEffects(creature, game).entrySet()) {
                        RequirementEffect effect = entry.getKey();
                        // get possible mustBeBlockedByAtLeastOne blocker
                        for (Ability ability : entry.getValue()) {
                            UUID toBeBlockedCreature = effect.mustBlockAttackerIfElseUnblocked(ability, game);
                            if (toBeBlockedCreature != null) {
                                Set<UUID> potentialBlockers;
                                if (mustBeBlockedByAtLeastOne.containsKey(toBeBlockedCreature)) {
                                    potentialBlockers = mustBeBlockedByAtLeastOne.get(toBeBlockedCreature);
                                } else {
                                    potentialBlockers = new HashSet<>();
                                    mustBeBlockedByAtLeastOne.put(toBeBlockedCreature, potentialBlockers);
                                }
                                potentialBlockers.add(creature.getId());
                            }
                        }

                        // check the mustBlockAny requirement ----------------------------------------
                        if (effect.mustBlockAny(game)) {
                            // check that it can block at least one of the attackers
                            // and no restictions prevent this
                            boolean mayBlock = false;
                            for (UUID attackingCreatureId : getAttackers()) {
                                if (creature.canBlock(attackingCreatureId, game)) {
                                    // check restrictions of the creature to block that prevent it can be blocked
                                    Permanent attackingCreature = game.getPermanent(attackingCreatureId);
                                    if (attackingCreature != null && attackingCreature.getMinBlockedBy() > 1) {
                                        // TODO: check if enough possible blockers are available, if true, mayBlock can be set to true

                                    } else {
                                        mayBlock = true;
                                        break;
                                    }
                                }
                            }
                            // if so inform human player or set block for AI player
                            if (mayBlock) {
                                if (controller.isHuman()) {
                                    game.informPlayer(controller, "Creature should block this turn: " + creature.getLogName());
                                } else {
                                    Player defender = game.getPlayer(creature.getControllerId());
                                    if (defender != null) {
                                        for (UUID attackingCreatureId : getAttackers()) {
                                            if (creature.canBlock(attackingCreatureId, game)) {
                                                defender.declareBlocker(defender.getId(), creature.getId(), attackingCreatureId, game);
                                                break;
                                            }
                                        }
                                    }
                                }
                                return false;
                            }
                        }

                    }
                }

            }

        }

        // check attacking creature mustBeBlockedByAtLeastOne
        for (UUID toBeBlockedCreatureId : mustBeBlockedByAtLeastOne.keySet()) {
            for (CombatGroup combatGroup : game.getCombat().getGroups()) {
                if (combatGroup.getBlockers().isEmpty() && combatGroup.getAttackers().contains(toBeBlockedCreatureId)) {
                    // creature is not blocked but has possible blockers
                    if (controller.isHuman()) {
                        Permanent toBeBlockedCreature = game.getPermanent(toBeBlockedCreatureId);
                        if (toBeBlockedCreature != null) {
                            // check if all possible blocker block other creatures they are forced to block
                            // read through all possible blockers
                            boolean possibleBlockerAvailable = false;
                            for (UUID possibleBlockerId : mustBeBlockedByAtLeastOne.get(toBeBlockedCreatureId)) {
                                Set<UUID> forcingAttackers = creatureMustBlockAttackers.get(possibleBlockerId);
                                if (forcingAttackers == null) {
                                    // no other creature forces the blocker to block -> it's available
                                    possibleBlockerAvailable = true;
                                    break;
                                }
                                // get the attackers he blocks
                                List<UUID> blockedAttackers = null;
                                for (CombatGroup combatGroupToCheck : game.getCombat().getGroups()) {
                                    if (combatGroupToCheck.getBlockers().contains(possibleBlockerId)) {
                                        blockedAttackers = combatGroupToCheck.getAttackers();
                                        break;
                                    }
                                }
                                if (blockedAttackers == null) {
                                    // he blocks no other creature -> it's available
                                    possibleBlockerAvailable = true;
                                    break;
                                }

                                // get attackers forcing the possible blocker to block
                                possibleBlockerAvailable = true;
                                for (UUID blockedAttackerId : blockedAttackers) {
                                    if (creatureMustBlockAttackers.get(possibleBlockerId).contains(blockedAttackerId)) {
                                        possibleBlockerAvailable = false;
                                        break;
                                    }
                                }
                                if (possibleBlockerAvailable) {
                                    break;
                                }
                            }
                            if (possibleBlockerAvailable) {
                                game.informPlayer(controller, new StringBuilder(toBeBlockedCreature.getLogName()).append(" has to be blocked by at least one creature.").toString());
                                return false;
                            }
                        }

                    } else {
                        // take the first potential blocker from the set to block for the AI
                        UUID blockingCreatureId = mustBeBlockedByAtLeastOne.get(toBeBlockedCreatureId).iterator().next();
                        Permanent blockingCreature = game.getPermanent(blockingCreatureId);
                        if (blockingCreature != null) {
                            Player defender = game.getPlayer(blockingCreature.getControllerId());
                            if (defender != null) {
                                defender.declareBlocker(defender.getId(), blockingCreatureId, toBeBlockedCreatureId, game);
                            }
                        }
                    }
                }
            }

        }
        // 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<UUID, Set<UUID>> entry : creatureMustBlockAttackers.entrySet()) {
            boolean blockIsValid;
            Permanent creatureForcedToBlock = game.getPermanent(entry.getKey());
            if (creatureForcedToBlock == null) {
                break;
            }
            // creature does not block -> not allowed
            if (creatureForcedToBlock.getBlocking() == 0) {
                blockIsValid = false;
            } else {
                blockIsValid = false;
                // which attacker is he blocking
                CombatGroups:
                for (CombatGroup combatGroup : game.getCombat().getGroups()) {
                    if (combatGroup.getBlockers().contains(creatureForcedToBlock.getId())) {
                        for (UUID forcingAttackerId : combatGroup.getAttackers()) {
                            if (entry.getValue().contains(forcingAttackerId)) {
                                // the creature is blocking a forcing attacker, so the block is ok
                                blockIsValid = true;
                                break CombatGroups;
                            } else {
                                // check if the blocker blocks a attacker that must be blocked at least by one and is the only blocker, this block is also valid
                                if (combatGroup.getBlockers().size() == 1) {
                                    if (mustBeBlockedByAtLeastOne.containsKey(forcingAttackerId)) {
                                        if (mustBeBlockedByAtLeastOne.get(forcingAttackerId).contains(creatureForcedToBlock.getId())) {
                                            blockIsValid = true;
                                            break CombatGroups;
                                        }
                                    }
                                }
                            }
                        }
                    }
                }

            }
            if (!blockIsValid) {
                sb.append(" ").append(creatureForcedToBlock.getLogName());
            }
        }
        if (sb.length() > 0) {
            sb.insert(0, "Some creatures are forced to block certain attacker(s):\n");
            sb.append("\nPlease block with each of these creatures an appropriate attacker.");
            game.informPlayer(controller, sb.toString());
            return false;
        }
        return true;
    }

    /**
     * Checks the canBeBlockedCheckAfter RestrictionEffect Is the block still
     * valid after all block decisions are done
     *
     * @param player
     * @param controller
     * @param game
     * @return
     */
    public boolean checkBlockRestrictionsAfter(Player player, Player controller, Game game) {
        for (UUID attackingCreatureId : this.getAttackers()) {
            Permanent attackingCreature = game.getPermanent(attackingCreatureId);
            if (attackingCreature != null) {
                for (Map.Entry<RestrictionEffect, HashSet<Ability>> 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.getLogName()).append(" can't be blocked this way.").toString());
                                return false;
                            } else {
                                // remove blocking creatures for AI
                                for (CombatGroup combatGroup : this.getGroups()) {
                                    if (combatGroup.getAttackers().contains(attackingCreatureId)) {
                                        for (UUID blockerId : combatGroup.getBlockers()) {
                                            removeBlocker(blockerId, game);
                                        }
                                    }
                                }

                            }
                        }
                    }
                }
            }
        }
        return true;
    }

    public void setDefenders(Game game) {
        Set<UUID> opponents = game.getOpponents(attackerId);
        PlayerList players;
        switch (game.getAttackOption()) {
            case LEFT:
                players = game.getState().getPlayerList(attackerId);
                while (true) {
                    Player opponent = players.getNext(game);
                    if (opponents.contains(opponent.getId())) {
                        addDefender(opponent.getId(), game);
                        break;
                    }
                }
                break;
            case RIGHT:
                players = game.getState().getPlayerList(attackerId);
                while (true) {
                    Player opponent = players.getPrevious(game);
                    if (opponents.contains(opponent.getId())) {
                        addDefender(opponent.getId(), game);
                        break;
                    }
                }
                break;
            case MULTIPLE:
                for (UUID opponentId : game.getOpponents(attackerId)) {
                    addDefender(opponentId, game);
                }
                break;
        }
    }

    private void addDefender(UUID defenderId, Game game) {
        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 boolean declareAttacker(UUID attackerId, UUID defenderId, Game game) {
        if (!defenders.contains(defenderId)) {
            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);
        if (!attacker.getAbilities().containsKey(VigilanceAbility.getInstance().getId())) {
            attacker.tap(game);
        }
        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<>();
            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
    public void addBlockingGroup(UUID blockerId, UUID attackerId, UUID playerId, Game game) {
        Permanent blocker = game.getPermanent(blockerId);
        if (blockerId != null && blocker != null && blocker.getBlocking() > 1) {
            if (!blockingGroups.containsKey(blockerId)) {
                CombatGroup newGroup = new CombatGroup(playerId, false, playerId);
                newGroup.blockers.add(blockerId);
                // add all blocked attackers
                for (CombatGroup group : groups) {
                    if (group.getBlockers().contains(blockerId)) {
                        // take into account banding
                        for (UUID attacker : group.attackers) {
                            newGroup.attackers.add(attacker);
                        }
                    }
                }
                blockingGroups.put(blockerId, newGroup);
            } else {
                //TODO: handle banding
                blockingGroups.get(blockerId).attackers.add(attackerId);
            }
        }
    }

    public boolean removeFromCombat(UUID creatureId, Game game) {
        boolean result = false;
        Permanent creature = game.getPermanent(creatureId);
        if (creature != null) {
            creature.setAttacking(false);
            creature.setBlocking(0);
            for (CombatGroup group : groups) {
                result |= group.remove(creatureId);
            }
        }
        return result;
    }

    public void endCombat(Game game) {
        Permanent creature;
        for (CombatGroup group : groups) {
            for (UUID attacker : group.attackers) {
                creature = game.getPermanent(attacker);
                if (creature != null) {
                    creature.setAttacking(false);
                    creature.setBlocking(0);
                }
            }
            for (UUID blocker : group.blockers) {
                creature = game.getPermanent(blocker);
                if (creature != null) {
                    creature.setAttacking(false);
                    creature.setBlocking(0);
                }
            }
        }
        clear();
    }

    public boolean hasFirstOrDoubleStrike(Game game) {
        for (CombatGroup group : groups) {
            if (group.hasFirstOrDoubleStrike(game)) {
                return true;
            }
        }
        return false;
    }

    public CombatGroup findGroup(UUID attackerId) {
        for (CombatGroup group : groups) {
            if (group.getAttackers().contains(attackerId)) {
                return group;
            }
        }
        return null;
    }

    public int totalUnblockedDamage(Game game) {
        int total = 0;
        for (CombatGroup group : groups) {
            if (group.getBlockers().isEmpty()) {
                total += group.totalAttackerDamage(game);
            }
        }
        return total;
    }

    public boolean attacksAlone() {
        return (groups.size() == 1 && groups.get(0).getAttackers().size() == 1);
    }

    public boolean noAttackers() {
        return groups.isEmpty() || getAttackers().isEmpty();
    }

    public boolean isAttacked(UUID defenderId, Game game) {
        for (CombatGroup group : groups) {
            if (group.getDefenderId().equals(defenderId)) {
                return true;
            }
            if (group.defenderIsPlaneswalker) {
                Permanent permanent = game.getPermanent(group.getDefenderId());
                if (permanent.getControllerId().equals(defenderId)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     *
     * @param attackerId
     * @return uuid of defending player or planeswalker
     */
    public UUID getDefenderId(UUID attackerId) {
        UUID defenderId = null;
        for (CombatGroup group : groups) {
            if (group.getAttackers().contains(attackerId)) {
                defenderId = group.getDefenderId();
                break;
            }
        }
        return defenderId;
    }

    public UUID getDefendingPlayerId(UUID attackerId, Game game) {
        UUID defenderId = null;
        for (CombatGroup group : groups) {
            if (group.getAttackers().contains(attackerId)) {
                defenderId = group.getDefenderId();
                if (group.defenderIsPlaneswalker) {
                    Permanent permanent = game.getPermanent(defenderId);
                    if (permanent != null) {
                        defenderId = permanent.getControllerId();
                    } else {
                        defenderId = null;
                    }
                }
                break;
            }
        }
        return defenderId;
    }

    public Set<UUID> getPlayerDefenders(Game game) {
        Set<UUID> playerDefenders = new HashSet<>();
        for (CombatGroup group : groups) {
            if (group.defenderIsPlaneswalker) {
                Permanent permanent = game.getPermanent(group.getDefenderId());
                if (permanent != null) {
                    playerDefenders.add(permanent.getControllerId());
                }
            } else {
                playerDefenders.add(group.getDefenderId());
            }
        }
        return playerDefenders;
    }

    public void damageAssignmentOrder(Game game) {
        for (CombatGroup group : groups) {
            group.pickBlockerOrder(attackerId, game);
        }
        for (Map.Entry<UUID, CombatGroup> blockingGroup : blockingGroups.entrySet()) {
            Permanent blocker = game.getPermanent(blockingGroup.getKey());
            if (blocker != null) {
                blockingGroup.getValue().pickAttackerOrder(blocker.getControllerId(), game);
            }
        }
    }

    public void removeAttacker(UUID attackerId, Game game) {
        for (CombatGroup group : groups) {
            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);
                    creature.setTapped(false);
                }
                if (group.attackers.isEmpty()) {
                    groups.remove(group);
                }
                return;
            }
        }
    }

   
    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)) {
                group.blockers.remove(blockerId);
                group.blockerOrder.remove(blockerId);
                if (group.blockers.isEmpty()) {
                    group.blocked = false;
                }
            }
        }
        Permanent creature = game.getPermanent(blockerId);
        if (creature != null) {
            creature.setBlocking(0);
        }
    }

    public UUID getAttackerId() {
        return attackerId;
    }

    public Map<UUID, Set<UUID>> getCreaturesForcedToAttack() {
        return creaturesForcedToAttack;
    }

    public int getMaxAttackers() {
        return maxAttackers;
    }

    @Override
    public Combat copy() {
        return new Combat(this);
    }

}
TOP

Related Classes of mage.game.combat.Combat

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.