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 e3254b3a02..75be926ecb 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 @@ -1742,7 +1742,7 @@ public class HumanPlayer extends PlayerImpl { if (ability instanceof PlayLandAbility) { return true; } - if (!ability.getSourceId().equals(getCastSourceIdWithAlternateMana()) + if (!getCastSourceIdWithAlternateMana().contains(ability.getSourceId()) && ability.getManaCostsToPay().convertedManaCost() > 0) { return true; } diff --git a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java index 0c1a2bfd5a..14834dc934 100644 --- a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java +++ b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java @@ -2374,20 +2374,25 @@ public class TestPlayer implements Player { } @Override - public UUID getCastSourceIdWithAlternateMana() { + public Set<UUID> getCastSourceIdWithAlternateMana() { return computerPlayer.getCastSourceIdWithAlternateMana(); } @Override - public ManaCosts getCastSourceIdManaCosts() { + public Map<UUID, ManaCosts<ManaCost>> getCastSourceIdManaCosts() { return computerPlayer.getCastSourceIdManaCosts(); } @Override - public Costs<Cost> getCastSourceIdCosts() { + public Map<UUID, Costs<Cost>> getCastSourceIdCosts() { return computerPlayer.getCastSourceIdCosts(); } + @Override + public void clearCastSourceIdManaCosts() { + computerPlayer.clearCastSourceIdManaCosts(); + } + @Override public boolean isInPayManaMode() { return computerPlayer.isInPayManaMode(); diff --git a/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java b/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java index 97465f1770..d604b41e58 100644 --- a/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java +++ b/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java @@ -19,8 +19,8 @@ import mage.counters.Counter; import mage.counters.Counters; import mage.designations.Designation; import mage.designations.DesignationType; -import mage.filter.FilterPermanent; import mage.filter.FilterMana; +import mage.filter.FilterPermanent; import mage.game.Game; import mage.game.Graveyard; import mage.game.Table; @@ -1208,7 +1208,7 @@ public class PlayerStub implements Player { } @Override - public UUID getCastSourceIdWithAlternateMana() { + public Set<UUID> getCastSourceIdWithAlternateMana() { return null; } @@ -1218,15 +1218,20 @@ public class PlayerStub implements Player { } @Override - public ManaCosts getCastSourceIdManaCosts() { + public Map<UUID, Costs<Cost>> getCastSourceIdCosts() { return null; } @Override - public Costs<Cost> getCastSourceIdCosts() { + public Map<UUID, ManaCosts<ManaCost>> getCastSourceIdManaCosts() { return null; } + @Override + public void clearCastSourceIdManaCosts() { + + } + @Override public void addPermissionToShowHandCards(UUID watcherUserId) { @@ -1374,19 +1379,19 @@ public class PlayerStub implements Player { @Override public void addPhyrexianToColors(FilterMana colors) { - + } @Override public void removePhyrexianFromColors(FilterMana colors) { - + } @Override public FilterMana getPhyrexianColors() { return (new FilterMana()); } - + @Override public SpellAbility chooseAbilityForCast(Card card, Game game, boolean noMana) { return card.getSpellAbility(); diff --git a/Mage/src/main/java/mage/abilities/SpellAbility.java b/Mage/src/main/java/mage/abilities/SpellAbility.java index 0a76898c13..c5da457462 100644 --- a/Mage/src/main/java/mage/abilities/SpellAbility.java +++ b/Mage/src/main/java/mage/abilities/SpellAbility.java @@ -63,7 +63,7 @@ public class SpellAbility extends ActivatedAbilityImpl { */ public boolean spellCanBeActivatedRegularlyNow(UUID playerId, Game game) { MageObject object = game.getObject(sourceId); - if ((Boolean) game.getState().getValue("CastFromExileEnabled" + object.getId()) != null) { + if (game.getState().getValue("CastFromExileEnabled" + object.getId()) != null) { return (Boolean) game.getState().getValue("CastFromExileEnabled" + object.getId()); // card like Chandra, Torch of Defiance +1 loyal ability) } return null != game.getContinuousEffects().asThough(sourceId, AsThoughEffectType.CAST_AS_INSTANT, this, playerId, game) // check this first to allow Offering in main phase @@ -98,7 +98,7 @@ public class SpellAbility extends ActivatedAbilityImpl { if (getSpellAbilityType() == SpellAbilityType.BASE_ALTERNATE) { Player player = game.getPlayer(playerId); if (player != null - && getSourceId().equals(player.getCastSourceIdWithAlternateMana())) { + && player.getCastSourceIdWithAlternateMana().contains(getSourceId())) { return ActivationStatus.getFalse(); } } diff --git a/Mage/src/main/java/mage/players/Player.java b/Mage/src/main/java/mage/players/Player.java index 1e8ddbb897..0bc22dbc94 100644 --- a/Mage/src/main/java/mage/players/Player.java +++ b/Mage/src/main/java/mage/players/Player.java @@ -20,8 +20,8 @@ import mage.counters.Counter; import mage.counters.Counters; import mage.designations.Designation; import mage.designations.DesignationType; -import mage.filter.FilterPermanent; import mage.filter.FilterMana; +import mage.filter.FilterPermanent; import mage.game.Game; import mage.game.Graveyard; import mage.game.Table; @@ -75,7 +75,7 @@ public interface Player extends MageItem, Copyable<Player> { void setLife(int life, Game game, UUID sourceId); /** - * @param amount amount of life loss + * @param amount amount of life loss * @param game * @param atCombat was the source combat damage * @return @@ -351,7 +351,7 @@ public interface Player extends MageItem, Copyable<Player> { * @param source * @param game * @param targetPlayerId player whose library will be searched - * @param triggerEvents whether searching will trigger any game events + * @param triggerEvents whether searching will trigger any game events * @return true if search was successful */ boolean searchLibrary(TargetCardInLibrary target, Ability source, Game game, UUID targetPlayerId, boolean triggerEvents); @@ -372,23 +372,23 @@ public interface Player extends MageItem, Copyable<Player> { /** * Plays a card if possible * - * @param card the card that can be cast + * @param card the card that can be cast * @param game - * @param noMana if it's a spell i can be cast without paying mana + * @param noMana if it's a spell i can be cast without paying mana * @param ignoreTiming if it's cast during the resolution of another spell - * no sorcery or play land timing restriction are checked. For a land it has - * to be the turn of the player playing that card. - * @param reference mage object that allows to play the card + * no sorcery or play land timing restriction are checked. For a land it has + * to be the turn of the player playing that card. + * @param reference mage object that allows to play the card * @return */ boolean playCard(Card card, Game game, boolean noMana, boolean ignoreTiming, MageObjectReference reference); /** - * @param card the land card to play + * @param card the land card to play * @param game * @param ignoreTiming false - it won't be checked if the stack is empty and - * you are able to play a Sorcery. It's still checked, if you are able to - * play a land concerning the number of lands you already played. + * you are able to play a Sorcery. It's still checked, if you are able to + * play a land concerning the number of lands you already played. * @return */ boolean playLand(Card card, Game game, boolean ignoreTiming); @@ -534,11 +534,11 @@ public interface Player extends MageItem, Copyable<Player> { /** * Moves the cards from cards to the bottom of the players library. * - * @param cards - list of cards that have to be moved - * @param game - game + * @param cards - list of cards that have to be moved + * @param game - game * @param anyOrder - true if player can determine the order of the cards - * else random order - * @param source - source ability + * else random order + * @param source - source ability * @return */ boolean putCardsOnBottomOfLibrary(Cards cards, Game game, Ability source, boolean anyOrder); @@ -559,10 +559,10 @@ public interface Player extends MageItem, Copyable<Player> { /** * Moves the cards from cards to the top of players library. * - * @param cards - list of cards that have to be moved - * @param game - game + * @param cards - list of cards that have to be moved + * @param game - game * @param anyOrder - true if player can determine the order of the cards - * @param source - source ability + * @param source - source ability * @return */ boolean putCardsOnTopOfLibrary(Cards cards, Game game, Ability source, boolean anyOrder); @@ -593,8 +593,8 @@ public interface Player extends MageItem, Copyable<Player> { /** * Choose the order in which blockers get damage assigned to * - * @param blockers list of blockers where to choose the next one from - * @param combatGroup the concerning combat group + * @param blockers list of blockers where to choose the next one from + * @param combatGroup the concerning combat group * @param blockerOrder the already set order of blockers * @param game * @return blocker next to add to the blocker order @@ -664,7 +664,7 @@ public interface Player extends MageItem, Copyable<Player> { * @param card * @param game * @param abilitiesToActivate extra info about abilities that can be - * activated on NO option + * activated on NO option * @return player looked at the card */ boolean lookAtFaceDownCard(Card card, Game game, int abilitiesToActivate); @@ -737,11 +737,11 @@ public interface Player extends MageItem, Copyable<Player> { * @param toZone * @param source * @param game - * @param tapped the cards are tapped on the battlefield - * @param faceDown the cards are face down in the to zone - * @param byOwner the card is moved (or put onto battlefield) by the owner - * of the card and if target zone is battlefield controls the permanent - * (instead of the controller of the source) + * @param tapped the cards are tapped on the battlefield + * @param faceDown the cards are face down in the to zone + * @param byOwner the card is moved (or put onto battlefield) by the owner + * of the card and if target zone is battlefield controls the permanent + * (instead of the controller of the source) * @param appliedEffects * @return */ @@ -777,7 +777,7 @@ public interface Player extends MageItem, Copyable<Player> { * list of applied effects is not saved * * @param card - * @param exileId exile zone id (optional) + * @param exileId exile zone id (optional) * @param exileName name of exile zone (optional) * @param sourceId * @param game @@ -819,7 +819,7 @@ public interface Player extends MageItem, Copyable<Player> { * @param sourceId * @param game * @param fromZone if null, this info isn't postet - * @param toTop to the top of the library else to the bottom + * @param toTop to the top of the library else to the bottom * @param withName show the card name in the log * @return */ @@ -844,18 +844,20 @@ public interface Player extends MageItem, Copyable<Player> { * without mana (null) or the mana set to manaCosts instead of its normal * mana costs. * - * @param sourceId the source that can be cast without mana + * @param sourceId the source that can be cast without mana * @param manaCosts alternate ManaCost, null if it can be cast without mana - * cost - * @param costs alternate other costs you need to pay + * cost + * @param costs alternate other costs you need to pay */ void setCastSourceIdWithAlternateMana(UUID sourceId, ManaCosts<ManaCost> manaCosts, Costs<Cost> costs); - UUID getCastSourceIdWithAlternateMana(); + Set<UUID> getCastSourceIdWithAlternateMana(); - ManaCosts<ManaCost> getCastSourceIdManaCosts(); + Map<UUID, ManaCosts<ManaCost>> getCastSourceIdManaCosts(); - Costs<Cost> getCastSourceIdCosts(); + Map<UUID, Costs<Cost>> getCastSourceIdCosts(); + + void clearCastSourceIdManaCosts(); // permission handling to show hand cards void addPermissionToShowHandCards(UUID watcherUserId); diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index ccf467dfe3..fa1e7b6598 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -32,8 +32,8 @@ import mage.counters.Counters; import mage.designations.Designation; import mage.designations.DesignationType; import mage.filter.FilterCard; -import mage.filter.FilterPermanent; import mage.filter.FilterMana; +import mage.filter.FilterPermanent; import mage.filter.common.FilterControlledPermanent; import mage.filter.common.FilterCreatureForCombat; import mage.filter.common.FilterCreatureForCombatBlock; @@ -160,9 +160,10 @@ public abstract class PlayerImpl implements Player, Serializable { protected boolean reachedNextTurnAfterLeaving = false; // indicates that the spell with the set sourceId can be cast with an alternate mana costs (can also be no mana costs) - protected UUID castSourceIdWithAlternateMana; - protected ManaCosts<ManaCost> castSourceIdManaCosts; - protected Costs<Cost> castSourceIdCosts; + // support multiple cards with alternative mana cost + protected Set<UUID> castSourceIdWithAlternateMana = new HashSet<>(); + protected Map<UUID, ManaCosts<ManaCost>> castSourceIdManaCosts = new HashMap<>(); + protected Map<UUID, Costs<Cost>> castSourceIdCosts = new HashMap<>(); // indicates that the player is in mana payment phase protected boolean payManaMode = false; @@ -271,9 +272,10 @@ public abstract class PlayerImpl implements Player, Serializable { this.priorityTimeLeft = player.getPriorityTimeLeft(); this.reachedNextTurnAfterLeaving = player.reachedNextTurnAfterLeaving; - this.castSourceIdWithAlternateMana = player.castSourceIdWithAlternateMana; - this.castSourceIdManaCosts = player.castSourceIdManaCosts; - this.castSourceIdCosts = player.castSourceIdCosts; + this.castSourceIdWithAlternateMana.addAll(player.castSourceIdWithAlternateMana); + this.castSourceIdManaCosts.putAll(player.castSourceIdManaCosts); + this.castSourceIdCosts.putAll(player.castSourceIdCosts); + this.payManaMode = player.payManaMode; this.phyrexianColors = player.phyrexianColors.copy(); @@ -340,9 +342,12 @@ public abstract class PlayerImpl implements Player, Serializable { this.turnControllers.clear(); this.turnControllers.addAll(player.getTurnControllers()); this.reachedNextTurnAfterLeaving = player.hasReachedNextTurnAfterLeaving(); - this.castSourceIdWithAlternateMana = player.getCastSourceIdWithAlternateMana(); - this.castSourceIdManaCosts = player.getCastSourceIdManaCosts(); - this.castSourceIdCosts = player.getCastSourceIdCosts(); + + this.clearCastSourceIdManaCosts(); + this.castSourceIdWithAlternateMana.addAll(player.getCastSourceIdWithAlternateMana()); + this.castSourceIdManaCosts.putAll(player.getCastSourceIdManaCosts()); + this.castSourceIdCosts.putAll(player.getCastSourceIdCosts()); + this.phyrexianColors = player.getPhyrexianColors().copy(); this.designations.clear(); @@ -417,9 +422,8 @@ public abstract class PlayerImpl implements Player, Serializable { this.setLife(game.getLife(), game, (UUID) null); this.setReachedNextTurnAfterLeaving(false); - this.castSourceIdWithAlternateMana = null; - this.castSourceIdManaCosts = null; - this.castSourceIdCosts = null; + this.clearCastSourceIdManaCosts(); + this.getManaPool().init(); // needed to remove mana that not empties on step change from previous game if left this.phyrexianColors = new FilterMana(); @@ -444,9 +448,7 @@ public abstract class PlayerImpl implements Player, Serializable { this.canPlayCardsFromGraveyard = false; this.topCardRevealed = false; this.alternativeSourceCosts.clear(); - this.castSourceIdWithAlternateMana = null; - this.castSourceIdManaCosts = null; - this.castSourceIdCosts = null; + this.clearCastSourceIdManaCosts(); this.getManaPool().clearEmptyManaPoolRules(); this.phyrexianColors = new FilterMana(); } @@ -617,7 +619,7 @@ public abstract class PlayerImpl implements Player, Serializable { if (abilities.containsKey(HexproofAbility.getInstance().getId())) { if (sourceControllerId != null && this.hasOpponent(sourceControllerId, game) && null == game.getContinuousEffects().asThough(this.getId(), - AsThoughEffectType.HEXPROOF, null, sourceControllerId, game)) { + AsThoughEffectType.HEXPROOF, null, sourceControllerId, game)) { return false; } } @@ -625,7 +627,7 @@ public abstract class PlayerImpl implements Player, Serializable { if (abilities.containsKey(HexproofFromWhiteAbility.getInstance().getId())) { if (sourceControllerId != null && this.hasOpponent(sourceControllerId, game) && null == game.getContinuousEffects().asThough(this.getId(), - AsThoughEffectType.HEXPROOF, null, sourceControllerId, game) + AsThoughEffectType.HEXPROOF, null, sourceControllerId, game) && source.getColor(game).isWhite()) { return false; } @@ -634,7 +636,7 @@ public abstract class PlayerImpl implements Player, Serializable { if (abilities.containsKey(HexproofFromBlueAbility.getInstance().getId())) { if (sourceControllerId != null && this.hasOpponent(sourceControllerId, game) && null == game.getContinuousEffects().asThough(this.getId(), - AsThoughEffectType.HEXPROOF, null, sourceControllerId, game) + AsThoughEffectType.HEXPROOF, null, sourceControllerId, game) && source.getColor(game).isBlue()) { return false; } @@ -643,7 +645,7 @@ public abstract class PlayerImpl implements Player, Serializable { if (abilities.containsKey(HexproofFromBlackAbility.getInstance().getId())) { if (sourceControllerId != null && this.hasOpponent(sourceControllerId, game) && null == game.getContinuousEffects().asThough(this.getId(), - AsThoughEffectType.HEXPROOF, null, sourceControllerId, game) + AsThoughEffectType.HEXPROOF, null, sourceControllerId, game) && source.getColor(game).isBlack()) { return false; } @@ -652,7 +654,7 @@ public abstract class PlayerImpl implements Player, Serializable { if (abilities.containsKey(HexproofFromMonocoloredAbility.getInstance().getId())) { if (sourceControllerId != null && this.hasOpponent(sourceControllerId, game) && null == game.getContinuousEffects().asThough(this.getId(), - AsThoughEffectType.HEXPROOF, null, sourceControllerId, game) + AsThoughEffectType.HEXPROOF, null, sourceControllerId, game) && !source.getColor(game).isColorless() && !source.getColor(game).isMulticolored()) { return false; @@ -695,7 +697,7 @@ public abstract class PlayerImpl implements Player, Serializable { game.informPlayers(getLogName() + " discards down to " + this.maxHandSize + (this.maxHandSize == 1 - ? " hand card" : " hand cards")); + ? " hand card" : " hand cards")); } discard(hand.size() - this.maxHandSize, null, game); } @@ -813,7 +815,7 @@ public abstract class PlayerImpl implements Player, Serializable { if (card != null) { GameEvent gameEvent = GameEvent.getEvent(GameEvent.EventType.DISCARD_CARD, card.getId(), source == null - ? null : source.getSourceId(), playerId); + ? null : source.getSourceId(), playerId); gameEvent.setFlag(source != null); // event from effect or from cost (source == null) if (!game.replaceEvent(gameEvent, source)) { // write info to game log first so game log infos from triggered or replacement effects follow in the game log @@ -828,7 +830,7 @@ public abstract class PlayerImpl implements Player, Serializable { // So discard is also successful if card is moved to another zone by replacement effect! game.fireEvent(GameEvent.getEvent(GameEvent.EventType.DISCARDED_CARD, card.getId(), source == null - ? null : source.getSourceId(), playerId)); + ? null : source.getSourceId(), playerId)); return true; } } @@ -1061,26 +1063,33 @@ public abstract class PlayerImpl implements Player, Serializable { @Override public void setCastSourceIdWithAlternateMana(UUID sourceId, ManaCosts<ManaCost> manaCosts, Costs<Cost> costs) { - castSourceIdWithAlternateMana = sourceId; - castSourceIdManaCosts = manaCosts; - castSourceIdCosts = costs; + castSourceIdWithAlternateMana.add(sourceId); + castSourceIdManaCosts.put(sourceId, manaCosts); + castSourceIdCosts.put(sourceId, costs); } @Override - public UUID getCastSourceIdWithAlternateMana() { + public Set<UUID> getCastSourceIdWithAlternateMana() { return castSourceIdWithAlternateMana; } @Override - public Costs<Cost> getCastSourceIdCosts() { + public Map<UUID, Costs<Cost>> getCastSourceIdCosts() { return castSourceIdCosts; } @Override - public ManaCosts getCastSourceIdManaCosts() { + public Map<UUID, ManaCosts<ManaCost>> getCastSourceIdManaCosts() { return castSourceIdManaCosts; } + @Override + public void clearCastSourceIdManaCosts() { + this.castSourceIdCosts.clear(); + this.castSourceIdManaCosts.clear(); + this.castSourceIdWithAlternateMana.clear(); + } + @Override public void setPayManaMode(boolean payManaMode) { this.payManaMode = payManaMode; @@ -1144,11 +1153,13 @@ public abstract class PlayerImpl implements Player, Serializable { } // Update the zcc to the stack ability.setSourceObjectZoneChangeCounter(game.getState().getZoneChangeCounter(ability.getSourceId())); + + // ALTERNATIVE COST from dynamic effects // some effects set sourceId to cast without paying mana costs or other costs - if (ability.getSourceId().equals(getCastSourceIdWithAlternateMana())) { + if (getCastSourceIdWithAlternateMana().contains(ability.getSourceId())) { Ability spellAbility = spell.getSpellAbility(); - ManaCosts alternateCosts = getCastSourceIdManaCosts(); - Costs<Cost> costs = getCastSourceIdCosts(); + ManaCosts alternateCosts = getCastSourceIdManaCosts().get(ability.getSourceId()); + Costs<Cost> costs = getCastSourceIdCosts().get(ability.getSourceId()); if (alternateCosts == null) { noMana = true; } else { @@ -1162,7 +1173,8 @@ public abstract class PlayerImpl implements Player, Serializable { spellAbility.getCosts().addAll(costs); } } - setCastSourceIdWithAlternateMana(null, null, null); + clearCastSourceIdManaCosts(); // TODO: test multiple alternative cost for different cards as same time + GameEvent event = GameEvent.getEvent(GameEvent.EventType.CAST_SPELL, spell.getSpellAbility().getId(), spell.getSpellAbility().getSourceId(), playerId, permittingObject); game.fireEvent(event); @@ -1506,7 +1518,7 @@ public abstract class PlayerImpl implements Player, Serializable { // Also called on the whole split card but only passing the fuse ability and other whole-split-card shared abilities // as candidates. private void getUseableActivatedAbilitiesHalfImpl(MageObject object, Zone zone, Game game, Abilities<Ability> candidateAbilites, - LinkedHashMap<UUID, ActivatedAbility> output) { + LinkedHashMap<UUID, ActivatedAbility> output) { boolean canUse = !(object instanceof Permanent) || ((Permanent) object).canUseActivatedAbilities(game); ManaOptions availableMana = null; // ManaOptions availableMana = getManaAvailable(game); // can only be activated if mana calculation works flawless otherwise player can't play spells they could play if calculation would work correctly @@ -1555,10 +1567,10 @@ public abstract class PlayerImpl implements Player, Serializable { != null // if anyone sees an issue with this code, please report it. Worked in my testing. || game.getContinuousEffects().asThough(object.getId(), - AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, - ability, - this.getId(), - game) + AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, + ability, + this.getId(), + game) != null) { if (canUse || ability.getAbilityType() == AbilityType.SPECIAL_ACTION) { @@ -1913,9 +1925,9 @@ public abstract class PlayerImpl implements Player, Serializable { } private List<Permanent> getPermanentsThatCanBeUntapped(Game game, - List<Permanent> canBeUntapped, - RestrictionUntapNotMoreThanEffect handledEffect, - Map<Entry<RestrictionUntapNotMoreThanEffect, Set<Ability>>, Integer> notMoreThanEffectsUsage) { + List<Permanent> canBeUntapped, + RestrictionUntapNotMoreThanEffect handledEffect, + Map<Entry<RestrictionUntapNotMoreThanEffect, Set<Ability>>, Integer> notMoreThanEffectsUsage) { List<Permanent> leftForUntap = new ArrayList<>(); // select permanents that can still be untapped for (Permanent permanent : canBeUntapped) { @@ -2623,7 +2635,7 @@ public abstract class PlayerImpl implements Player, Serializable { @Override public boolean searchLibrary(TargetCardInLibrary target, Ability source, Game game, UUID targetPlayerId, - boolean triggerEvents) { + boolean triggerEvents) { //20091005 - 701.14c Library searchedLibrary = null; String searchInfo = null; @@ -2827,7 +2839,7 @@ public abstract class PlayerImpl implements Player, Serializable { /** * @param game * @param appliedEffects - * @param numSides Number of sides the dice has + * @param numSides Number of sides the dice has * @return the number that the player rolled */ @Override @@ -2864,16 +2876,16 @@ public abstract class PlayerImpl implements Player, Serializable { /** * @param game * @param appliedEffects - * @param numberChaosSides The number of chaos sides the planar die - * currently has (normally 1 but can be 5) + * @param numberChaosSides The number of chaos sides the planar die + * currently has (normally 1 but can be 5) * @param numberPlanarSides The number of chaos sides the planar die - * currently has (normally 1) + * currently has (normally 1) * @return the outcome that the player rolled. Either ChaosRoll, PlanarRoll * or NilRoll */ @Override public PlanarDieRoll rollPlanarDie(Game game, ArrayList<UUID> appliedEffects, int numberChaosSides, - int numberPlanarSides) { + int numberPlanarSides) { int result = RandomUtil.nextInt(9) + 1; PlanarDieRoll roll = PlanarDieRoll.NIL_ROLL; if (numberChaosSides + numberPlanarSides > 9) { @@ -3030,7 +3042,7 @@ public abstract class PlayerImpl implements Player, Serializable { /** * @param ability - * @param available if null, it won't be checked if enough mana is available + * @param available if null, it won't be checked if enough mana is available * @param sourceObject * @param game * @return @@ -3093,6 +3105,7 @@ public abstract class PlayerImpl implements Player, Serializable { } } + // ALTERNATIVE COST from source card (AlternativeCostSourceAbility) for (Ability objectAbility : sourceObject.getAbilities()) { if (objectAbility instanceof AlternativeCostSourceAbility) { if (objectAbility.getCosts().canPay(copy, copy.getSourceId(), playerId, game)) { @@ -3100,7 +3113,27 @@ public abstract class PlayerImpl implements Player, Serializable { } } } - return canPlayCardByAlternateCost(card, available, ability, game); + + // ALTERNATIVE COST FROM dynamic effects + if (getCastSourceIdWithAlternateMana().contains(copy.getSourceId())) { + ManaCosts alternateCosts = getCastSourceIdManaCosts().get(copy.getSourceId()); + Costs<Cost> costs = getCastSourceIdCosts().get(copy.getSourceId()); + + boolean canPutToPlay = true; + if (alternateCosts != null && !alternateCosts.canPay(copy, copy.getSourceId(), playerId, game)) { + canPutToPlay = false; + } + if (costs != null && !costs.canPay(copy, copy.getSourceId(), playerId, game)) { + canPutToPlay = false; + } + + if (canPutToPlay) { + return true; + } + } + + // ALTERNATIVE COST from source card (any AlternativeSourceCosts) + return canPlayCardByAlternateCost(card, available, copy, game); } return false; } @@ -3326,8 +3359,8 @@ public abstract class PlayerImpl implements Player, Serializable { if (ability instanceof ActivatedAbility) { if (!(ability instanceof PlayLandAbility) || !game.getContinuousEffects().preventedByRuleModification( - GameEvent.getEvent(GameEvent.EventType.PLAY_LAND, ability.getSourceId(), - ability.getSourceId(), playerId), ability, game, true)) { + GameEvent.getEvent(GameEvent.EventType.PLAY_LAND, ability.getSourceId(), + ability.getSourceId(), playerId), ability, game, true)) { if (canPlay((ActivatedAbility) ability, availableMana, card, game)) { playable.add(ability); } @@ -3648,7 +3681,7 @@ public abstract class PlayerImpl implements Player, Serializable { @Override public boolean canPaySacrificeCost(Permanent permanent, UUID sourceId, - UUID controllerId, Game game + UUID controllerId, Game game ) { return sacrificeCostFilter == null || !sacrificeCostFilter.match(permanent, sourceId, controllerId, game); } @@ -3802,8 +3835,8 @@ public abstract class PlayerImpl implements Player, Serializable { @Override public boolean moveCards(Card card, Zone toZone, - Ability source, Game game, - boolean tapped, boolean faceDown, boolean byOwner, List<UUID> appliedEffects + Ability source, Game game, + boolean tapped, boolean faceDown, boolean byOwner, List<UUID> appliedEffects ) { Set<Card> cardList = new HashSet<>(); if (card != null) { @@ -3814,22 +3847,22 @@ public abstract class PlayerImpl implements Player, Serializable { @Override public boolean moveCards(Cards cards, Zone toZone, - Ability source, Game game + Ability source, Game game ) { return moveCards(cards.getCards(game), toZone, source, game); } @Override public boolean moveCards(Set<Card> cards, Zone toZone, - Ability source, Game game + Ability source, Game game ) { return moveCards(cards, toZone, source, game, false, false, false, null); } @Override public boolean moveCards(Set<Card> cards, Zone toZone, - Ability source, Game game, - boolean tapped, boolean faceDown, boolean byOwner, List<UUID> appliedEffects + Ability source, Game game, + boolean tapped, boolean faceDown, boolean byOwner, List<UUID> appliedEffects ) { if (cards.isEmpty()) { return true; @@ -3924,8 +3957,8 @@ public abstract class PlayerImpl implements Player, Serializable { @Override public boolean moveCardsToExile(Card card, Ability source, - Game game, boolean withName, UUID exileId, - String exileZoneName + Game game, boolean withName, UUID exileId, + String exileZoneName ) { Set<Card> cards = new HashSet<>(); cards.add(card); @@ -3934,8 +3967,8 @@ public abstract class PlayerImpl implements Player, Serializable { @Override public boolean moveCardsToExile(Set<Card> cards, Ability source, - Game game, boolean withName, UUID exileId, - String exileZoneName + Game game, boolean withName, UUID exileId, + String exileZoneName ) { if (cards.isEmpty()) { return true; @@ -3951,14 +3984,14 @@ public abstract class PlayerImpl implements Player, Serializable { @Override public boolean moveCardToHandWithInfo(Card card, UUID sourceId, - Game game + Game game ) { return this.moveCardToHandWithInfo(card, sourceId, game, true); } @Override public boolean moveCardToHandWithInfo(Card card, UUID sourceId, - Game game, boolean withName + Game game, boolean withName ) { boolean result = false; Zone fromZone = game.getState().getZone(card.getId()); @@ -3983,7 +4016,7 @@ public abstract class PlayerImpl implements Player, Serializable { @Override public Set<Card> moveCardsToGraveyardWithInfo(Set<Card> allCards, Ability source, - Game game, Zone fromZone + Game game, Zone fromZone ) { UUID sourceId = source == null ? null : source.getSourceId(); Set<Card> movedCards = new LinkedHashSet<>(); @@ -3991,7 +4024,7 @@ public abstract class PlayerImpl implements Player, Serializable { // identify cards from one owner Cards cards = new CardsImpl(); UUID ownerId = null; - for (Iterator<Card> it = allCards.iterator(); it.hasNext();) { + for (Iterator<Card> it = allCards.iterator(); it.hasNext(); ) { Card card = it.next(); if (cards.isEmpty()) { ownerId = card.getOwnerId(); @@ -4054,7 +4087,7 @@ public abstract class PlayerImpl implements Player, Serializable { @Override public boolean moveCardToGraveyardWithInfo(Card card, UUID sourceId, - Game game, Zone fromZone + Game game, Zone fromZone ) { if (card == null) { return false; @@ -4083,8 +4116,8 @@ public abstract class PlayerImpl implements Player, Serializable { @Override public boolean moveCardToLibraryWithInfo(Card card, UUID sourceId, - Game game, Zone fromZone, - boolean toTop, boolean withName + Game game, Zone fromZone, + boolean toTop, boolean withName ) { if (card == null) { return false; @@ -4118,7 +4151,7 @@ public abstract class PlayerImpl implements Player, Serializable { @Override public boolean moveCardToExileWithInfo(Card card, UUID exileId, String exileName, UUID sourceId, - Game game, Zone fromZone, boolean withName) { + Game game, Zone fromZone, boolean withName) { if (card == null) { return false; } @@ -4141,7 +4174,7 @@ public abstract class PlayerImpl implements Player, Serializable { game.informPlayers(this.getLogName() + " moves " + (withName ? card.getLogName() + (card.isCopy() ? " (Copy)" : "") : "a card face down") + ' ' + (fromZone != null ? "from " + fromZone.toString().toLowerCase(Locale.ENGLISH) - + ' ' : "") + "to the exile zone"); + + ' ' : "") + "to the exile zone"); } result = true;