forge.game.mana.ManaPool.java Source code

Java tutorial

Introduction

Here is the source code for forge.game.mana.ManaPool.java

Source

/*
 * Forge: Play Magic: the Gathering.
 * Copyright (C) 2011  Forge Team
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package forge.game.mana;

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;

import forge.GameCommand;
import forge.card.ColorSet;
import forge.card.MagicColor;
import forge.card.mana.ManaCostShard;
import forge.game.GlobalRuleChange;
import forge.game.card.Card;
import forge.game.event.EventValueChangeType;
import forge.game.event.GameEventCardStatsChanged;
import forge.game.event.GameEventManaPool;
import forge.game.event.GameEventZone;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.AbilityManaPart;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;

import org.apache.commons.lang3.StringUtils;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

/**
 * <p>
 * ManaPool class.
 * </p>
 * 
 * @author Forge
 * @version $Id: ManaPool.java 29414 2015-05-17 11:28:21Z elcnesh $
 */
public class ManaPool implements Iterable<Mana> {
    private final Player owner;
    private final Multimap<Byte, Mana> floatingMana = ArrayListMultimap.create();

    public ManaPool(final Player player) {
        owner = player;
        restoreColorReplacements();
    }

    public final int getAmountOfColor(final byte color) {
        Collection<Mana> ofColor = floatingMana.get(color);
        return ofColor == null ? 0 : ofColor.size();
    }

    public void addMana(final Mana mana) {
        floatingMana.put(mana.getColor(), mana);
        owner.updateManaForView();
        owner.getGame().fireEvent(new GameEventManaPool(owner, EventValueChangeType.Added, mana));
    }

    public final void add(final Iterable<Mana> manaList) {
        for (final Mana m : manaList) {
            addMana(m);
        }
    }

    /**
     * <p>
     * willManaBeLostAtEndOfPhase.
     * 
     * @return - whether floating mana will be lost if the current phase ended right now
     * </p>
     */
    public final boolean willManaBeLostAtEndOfPhase() {
        if (floatingMana.isEmpty()
                || owner.getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.manapoolsDontEmpty)
                || owner.hasKeyword("Convert unused mana to Colorless")) {
            return false;
        }

        int safeMana = 0;
        for (final byte c : MagicColor.WUBRG) {
            final String captName = StringUtils.capitalize(MagicColor.toLongString(c));
            if (owner.hasKeyword(captName + " mana doesn't empty from your mana pool as steps and phases end.")) {
                safeMana += getAmountOfColor(c);
            }
        }

        if (totalMana() == safeMana) {
            return false; //won't lose floating mana if all mana is of colors that aren't going to be emptied
        }
        return true;
    }

    public final List<Mana> clearPool(boolean isEndOfPhase) {
        // isEndOfPhase parameter: true = end of phase, false = mana drain effect
        List<Mana> cleared = new ArrayList<Mana>();
        if (floatingMana.isEmpty()) {
            return cleared;
        }

        if (isEndOfPhase
                && owner.getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.manapoolsDontEmpty)) {
            return cleared;
        }

        final boolean convertToColorless = owner.hasKeyword("Convert unused mana to Colorless");

        final List<Byte> keys = Lists.newArrayList(floatingMana.keySet());
        if (isEndOfPhase) {
            for (final Byte c : Lists.newArrayList(keys)) {
                final String captName = StringUtils.capitalize(MagicColor.toLongString(c));
                if (owner.hasKeyword(
                        captName + " mana doesn't empty from your mana pool as steps and phases end.")) {
                    keys.remove(c);
                }
            }
        }
        if (convertToColorless) {
            keys.remove(Byte.valueOf(MagicColor.COLORLESS));
        }

        for (Byte b : keys) {
            Collection<Mana> cm = floatingMana.get(b);
            if (isEndOfPhase && !owner.getGame().getPhaseHandler().is(PhaseType.CLEANUP)) {
                final List<Mana> pMana = new ArrayList<Mana>();
                for (final Mana mana : cm) {
                    if (mana.getManaAbility() != null && mana.getManaAbility().isPersistentMana()) {
                        pMana.add(mana);
                    }
                }
                cm.removeAll(pMana);
                if (convertToColorless) {
                    convertManaColor(b, MagicColor.COLORLESS);
                    cm.addAll(pMana);
                } else {
                    cleared.addAll(cm);
                    cm.clear();
                    floatingMana.putAll(b, pMana);
                }
            } else {
                if (convertToColorless) {
                    convertManaColor(b, MagicColor.COLORLESS);
                } else {
                    cleared.addAll(cm);
                    cm.clear();
                }
            }
        }

        owner.updateManaForView();
        owner.getGame().fireEvent(new GameEventManaPool(owner, EventValueChangeType.Cleared, null));
        return cleared;
    }

    private void convertManaColor(final byte originalColor, final byte toColor) {
        List<Mana> convert = new ArrayList<Mana>();
        Collection<Mana> cm = floatingMana.get(originalColor);
        for (Mana m : cm) {
            convert.add(new Mana(toColor, m.getSourceCard(), m.getManaAbility()));
        }
        cm.clear();
        floatingMana.putAll(toColor, convert);
        owner.updateManaForView();
    }

    private void removeMana(final Mana mana) {
        Collection<Mana> cm = floatingMana.get(mana.getColor());
        if (cm.remove(mana)) {
            owner.updateManaForView();
            owner.getGame().fireEvent(new GameEventManaPool(owner, EventValueChangeType.Removed, mana));
        }
    }

    public final void payManaFromAbility(final SpellAbility saPaidFor, ManaCostBeingPaid manaCost,
            final SpellAbility saPayment) {
        // Mana restriction must be checked before this method is called
        final List<SpellAbility> paidAbs = saPaidFor.getPayingManaAbilities();
        AbilityManaPart abManaPart = saPayment.getManaPartRecursive();

        paidAbs.add(saPayment); // assumes some part on the mana produced by the ability will get used
        for (final Mana mana : abManaPart.getLastManaProduced()) {
            if (tryPayCostWithMana(saPaidFor, manaCost, mana)) {
                saPaidFor.getPayingMana().add(0, mana);
            }
        }
    }

    public boolean tryPayCostWithColor(byte colorCode, SpellAbility saPaidFor, ManaCostBeingPaid manaCost) {
        Mana manaFound = null;
        String restriction = manaCost.getSourceRestriction();
        Collection<Mana> cm = floatingMana.get(colorCode);

        for (final Mana mana : cm) {
            if (mana.getManaAbility() != null && !mana.getManaAbility().meetsManaRestrictions(saPaidFor)) {
                continue;
            }

            if (StringUtils.isNotBlank(restriction) && !mana.getSourceCard().getType().hasStringType(restriction)) {
                continue;
            }

            manaFound = mana;
            break;
        }

        if (manaFound != null && tryPayCostWithMana(saPaidFor, manaCost, manaFound)) {
            saPaidFor.getPayingMana().add(0, manaFound);
            return true;
        }
        return false;
    }

    public boolean tryPayCostWithMana(final SpellAbility sa, ManaCostBeingPaid manaCost, final Mana mana) {
        if (!manaCost.isNeeded(mana, this)) {
            return false;
        }
        manaCost.payMana(mana, this);
        removeMana(mana);
        if (mana.addsNoCounterMagic(sa) && sa.getHostCard() != null) {
            sa.getHostCard().setCanCounter(false);
        }
        if (sa.isSpell() && sa.getHostCard() != null) {
            final Card host = sa.getHostCard();
            if (mana.addsKeywords(sa) && mana.addsKeywordsType()
                    && host.getType().hasStringType(mana.getManaAbility().getAddsKeyowrdsType())) {
                final long timestamp = sa.getHostCard().getGame().getNextTimestamp();
                final List<String> kws = Arrays.asList(mana.getAddedKeywords().split(" & "));
                host.addChangedCardKeywords(kws, new ArrayList<String>(), false, timestamp);
                if (mana.addsKeywordsUntil()) {
                    final GameCommand untilEOT = new GameCommand() {
                        private static final long serialVersionUID = -8285169579025607693L;

                        @Override
                        public void run() {
                            if (!kws.isEmpty()) {
                                for (String kw : kws) {
                                    if (kw.startsWith("HIDDEN")) {
                                        sa.getHostCard().removeHiddenExtrinsicKeyword(kw);
                                    }
                                }
                                host.removeChangedCardKeywords(timestamp);
                            }
                            host.getGame().fireEvent(new GameEventCardStatsChanged(host));
                        }
                    };
                    String until = mana.getManaAbility().getAddsKeywordsUntil();
                    if ("UntilEOT".equals(until)) {
                        host.getGame().getEndOfTurn().addUntil(untilEOT);
                    }
                }
            }
            if (mana.addsCounters(sa)) {
                mana.getManaAbility().createETBCounters(sa.getHostCard());
            }
        }
        return true;
    }

    public final boolean isEmpty() {
        return floatingMana.isEmpty();
    }

    public final int totalMana() {
        return floatingMana.values().size();
    }

    //Account for mana part of ability when undoing it
    public boolean accountFor(final AbilityManaPart ma) {
        if (ma == null) {
            return false;
        }
        if (floatingMana.isEmpty()) {
            return false;
        }

        final List<Mana> removeFloating = new ArrayList<Mana>();

        boolean manaNotAccountedFor = false;
        // loop over mana produced by mana ability
        for (Mana mana : ma.getLastManaProduced()) {
            Collection<Mana> poolLane = floatingMana.get(mana.getColor());

            if (poolLane != null && poolLane.contains(mana)) {
                removeFloating.add(mana);
            } else {
                manaNotAccountedFor = true;
                break;
            }
        }

        // When is it legitimate for all the mana not to be accountable?
        // Does this condition really indicate an bug in Forge?
        if (manaNotAccountedFor) {
            return false;
        }

        for (int k = 0; k < removeFloating.size(); k++) {
            removeMana(removeFloating.get(k));
        }
        return true;
    }

    public final void refundManaPaid(final SpellAbility sa) {
        // Send all mana back to your mana pool, before accounting for it.
        final List<Mana> manaPaid = sa.getPayingMana();

        // move non-undoable paying mana back to floating
        if (sa.getHostCard() != null) {
            sa.getHostCard().setCanCounter(true);
        }
        for (final Mana m : manaPaid) {
            addMana(m);
        }
        manaPaid.clear();

        List<SpellAbility> payingAbilities = sa.getPayingManaAbilities();
        for (final SpellAbility am : payingAbilities) {
            // undo paying abilities if we can
            am.undo();
        }

        for (final SpellAbility am : payingAbilities) {
            // Recursively refund abilities that were used.
            refundManaPaid(am);
        }

        payingAbilities.clear();

        // update battlefield of activating player - to redraw cards used to pay mana as untapped
        Player p = sa.getActivatingPlayer();
        p.getGame().fireEvent(new GameEventZone(ZoneType.Battlefield, p, EventValueChangeType.ComplexUpdate, null));
    }

    // Conversion matrix ORs byte values to make mana more payable
    // Restrictive matrix ANDs byte values to make mana less payable
    private final byte[] colorConversionMatrix = new byte[MagicColor.WUBRG.length + 1];
    private final byte[] colorRestrictionMatrix = new byte[MagicColor.WUBRG.length + 1];
    private static final byte[] identityMatrix = { MagicColor.WHITE, MagicColor.BLUE, MagicColor.BLACK,
            MagicColor.RED, MagicColor.GREEN, 0 };

    public void adjustColorReplacement(byte originalColor, byte replacementColor, boolean additive) {
        // Fix the index without hardcodes
        int rowIdx = MagicColor.getIndexOfFirstColor(originalColor);
        rowIdx = rowIdx < 0 ? identityMatrix.length - 1 : rowIdx;
        if (additive) {
            colorConversionMatrix[rowIdx] |= replacementColor;
        } else {
            colorRestrictionMatrix[rowIdx] &= replacementColor;
        }
    }

    public void restoreColorReplacements() {
        for (int i = 0; i < colorConversionMatrix.length; i++) {
            colorConversionMatrix[i] = identityMatrix[i];
        }
        for (int i = 0; i < colorRestrictionMatrix.length; i++) {
            colorRestrictionMatrix[i] = ColorSet.ALL_COLORS.getColor();
        }
    }

    public byte getPossibleColorUses(byte color) {
        // Take the current conversion value, AND with restrictions to get mana usage
        int rowIdx = MagicColor.getIndexOfFirstColor(color);
        int matrixIdx = rowIdx < 0 ? identityMatrix.length - 1 : rowIdx;

        byte colorUse = colorConversionMatrix[matrixIdx];
        colorUse &= colorRestrictionMatrix[matrixIdx];
        return colorUse;
    }

    public boolean canPayForShardWithColor(ManaCostShard shard, byte color) {
        byte line = getPossibleColorUses(color);
        for (int i = 0; i < MagicColor.NUMBER_OR_COLORS; i++) {
            byte outColor = MagicColor.WUBRG[i];
            if ((line & outColor) != 0 && shard.canBePaidWithManaOfColor(outColor)) {
                return true;
            }
        }
        return shard.canBePaidWithManaOfColor((byte) 0);
    }

    @Override
    public Iterator<Mana> iterator() {
        return floatingMana.values().iterator();
    }
}