mirror of
https://github.com/magefree/mage.git
synced 2025-12-26 05:22:02 -08:00
Merge origin/master
Conflicts: Mage/src/main/java/mage/players/PlayerImpl.java
This commit is contained in:
commit
b7b3bc4474
51 changed files with 979 additions and 396 deletions
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
package mage.abilities.effects;
|
||||
|
||||
import java.util.UUID;
|
||||
import mage.abilities.Ability;
|
||||
import mage.constants.AsThoughEffectType;
|
||||
import mage.constants.Duration;
|
||||
|
|
@ -8,8 +7,9 @@ import mage.constants.EffectType;
|
|||
import mage.constants.Outcome;
|
||||
import mage.game.Game;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author BetaSteward_at_googlemail.com
|
||||
*/
|
||||
public abstract class AsThoughEffectImpl extends ContinuousEffectImpl implements AsThoughEffect {
|
||||
|
|
@ -29,10 +29,11 @@ public abstract class AsThoughEffectImpl extends ContinuousEffectImpl implements
|
|||
|
||||
@Override
|
||||
public boolean applies(UUID objectId, Ability affectedAbility, Ability source, Game game, UUID playerId) {
|
||||
// affectedControllerId = player to check
|
||||
if (getAsThoughEffectType().equals(AsThoughEffectType.LOOK_AT_FACE_DOWN)) {
|
||||
return applies(objectId, source, playerId, game);
|
||||
} else {
|
||||
return applies(objectId, source, affectedAbility.getControllerId(), game);
|
||||
return applies(objectId, source, playerId, game);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -505,12 +505,19 @@ public class ContinuousEffects implements Serializable {
|
|||
UUID idToCheck;
|
||||
if (affectedAbility != null && affectedAbility.getSourceObject(game) instanceof SplitCardHalf) {
|
||||
idToCheck = ((SplitCardHalf) affectedAbility.getSourceObject(game)).getParentCard().getId();
|
||||
} else if (affectedAbility != null && affectedAbility.getSourceObject(game) instanceof AdventureCardSpell
|
||||
&& type != AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE
|
||||
&& type != AsThoughEffectType.CAST_AS_INSTANT) {
|
||||
// adventure spell uses alternative characteristics for spell/stack
|
||||
idToCheck = ((AdventureCardSpell) affectedAbility.getSourceObject(game)).getParentCard().getId();
|
||||
} else {
|
||||
Card card = game.getCard(objectId);
|
||||
if (card != null && card instanceof SplitCardHalf) {
|
||||
if (card instanceof SplitCardHalf) {
|
||||
idToCheck = ((SplitCardHalf) card).getParentCard().getId();
|
||||
} else if (card != null && type == AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE
|
||||
&& card instanceof AdventureCardSpell) {
|
||||
} else if (card instanceof AdventureCardSpell
|
||||
&& type != AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE
|
||||
&& type != AsThoughEffectType.CAST_AS_INSTANT) {
|
||||
// adventure spell uses alternative characteristics for spell/stack
|
||||
idToCheck = ((AdventureCardSpell) card).getParentCard().getId();
|
||||
} else {
|
||||
idToCheck = objectId;
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ public class ExileAdventureSpellEffect extends OneShotEffect implements MageSing
|
|||
Spell spell = game.getStack().getSpell(source.getId());
|
||||
if (spell != null && !spell.isCopy()) {
|
||||
Card spellCard = spell.getCard();
|
||||
if (spellCard != null && spellCard instanceof AdventureCardSpell) {
|
||||
if (spellCard instanceof AdventureCardSpell) {
|
||||
UUID exileId = adventureExileId(controller.getId(), game);
|
||||
game.getExile().createZone(exileId, "On an Adventure");
|
||||
AdventureCardSpell adventureSpellCard = (AdventureCardSpell) spellCard;
|
||||
|
|
|
|||
|
|
@ -1,11 +1,6 @@
|
|||
|
||||
package mage.abilities.effects.common.continuous;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import mage.MageObject;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.SpellAbility;
|
||||
import mage.abilities.effects.AsThoughEffectImpl;
|
||||
import mage.cards.Card;
|
||||
import mage.constants.AsThoughEffectType;
|
||||
|
|
@ -15,6 +10,8 @@ import mage.filter.FilterCard;
|
|||
import mage.game.Game;
|
||||
import mage.players.Player;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author nantuko
|
||||
*/
|
||||
|
|
@ -55,26 +52,18 @@ public class PlayTheTopCardEffect extends AsThoughEffectImpl {
|
|||
|
||||
@Override
|
||||
public boolean applies(UUID objectId, Ability affectedAbility, Ability source, Game game, UUID playerId) {
|
||||
Card cardOnTop = game.getCard(objectId);
|
||||
Card cardToCheckProperties = cardOnTop;
|
||||
Card cardToCheck = game.getCard(objectId);
|
||||
objectId = game.getCard(objectId).getMainCard().getId(); // for split cards
|
||||
|
||||
// Check each ability individually, as e.g. Adventures and associated creatures may get different results from the filter.
|
||||
if (affectedAbility != null) {
|
||||
MageObject sourceObject = affectedAbility.getSourceObject(game);
|
||||
if (sourceObject != null && sourceObject instanceof Card) {
|
||||
cardToCheckProperties = (Card) sourceObject;
|
||||
}
|
||||
}
|
||||
|
||||
if (cardOnTop != null
|
||||
if (cardToCheck != null
|
||||
&& playerId.equals(source.getControllerId())
|
||||
&& cardOnTop.isOwnedBy(source.getControllerId())
|
||||
&& (!cardToCheckProperties.getManaCost().isEmpty() || cardToCheckProperties.isLand())
|
||||
&& filter.match(cardToCheckProperties, game)) {
|
||||
Player player = game.getPlayer(cardOnTop.getOwnerId());
|
||||
if (player != null && cardOnTop.equals(player.getLibrary().getFromTop(game))) {
|
||||
return true;
|
||||
}
|
||||
&& cardToCheck.isOwnedBy(source.getControllerId())
|
||||
&& (!cardToCheck.getManaCost().isEmpty() || cardToCheck.isLand())
|
||||
&& filter.match(cardToCheck, game)) {
|
||||
Player player = game.getPlayer(cardToCheck.getOwnerId());
|
||||
|
||||
UUID needCardID = player.getLibrary().getFromTop(game) == null ? null : player.getLibrary().getFromTop(game).getId();
|
||||
return objectId.equals(needCardID);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -100,6 +100,7 @@ class HideawayExileEffect extends OneShotEffect {
|
|||
cards.addAll(controller.getLibrary().getTopCards(game, 4));
|
||||
if (!cards.isEmpty()) {
|
||||
TargetCard target1 = new TargetCard(Zone.LIBRARY, filter1);
|
||||
target1.setNotTarget(true);
|
||||
if (controller.choose(Outcome.Detriment, cards, target1, game)) {
|
||||
Card card = cards.get(target1.getFirstTarget(), game);
|
||||
if (card != null) {
|
||||
|
|
|
|||
|
|
@ -1,16 +1,10 @@
|
|||
|
||||
package mage.abilities.keyword;
|
||||
|
||||
import java.util.Iterator;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.SpellAbility;
|
||||
import mage.abilities.StaticAbility;
|
||||
import mage.abilities.TriggeredAbilityImpl;
|
||||
import mage.abilities.costs.Cost;
|
||||
import mage.abilities.costs.Costs;
|
||||
import mage.abilities.costs.OptionalAdditionalCost;
|
||||
import mage.abilities.costs.OptionalAdditionalCostImpl;
|
||||
import mage.abilities.costs.OptionalAdditionalSourceCosts;
|
||||
import mage.abilities.costs.*;
|
||||
import mage.abilities.costs.mana.ManaCostsImpl;
|
||||
import mage.abilities.effects.Effect;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
|
|
@ -23,8 +17,9 @@ import mage.game.stack.Spell;
|
|||
import mage.game.stack.StackObject;
|
||||
import mage.players.Player;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author LevelX2
|
||||
*/
|
||||
public class ReplicateAbility extends StaticAbility implements OptionalAdditionalSourceCosts {
|
||||
|
|
@ -91,12 +86,12 @@ public class ReplicateAbility extends StaticAbility implements OptionalAdditiona
|
|||
String times = "";
|
||||
if (additionalCost.isRepeatable()) {
|
||||
int numActivations = additionalCost.getActivateCount();
|
||||
times = Integer.toString(numActivations + 1) + (numActivations == 0 ? " time " : " times ");
|
||||
times = (numActivations + 1) + (numActivations == 0 ? " time " : " times ");
|
||||
}
|
||||
if (additionalCost.canPay(ability, sourceId, controllerId, game)
|
||||
&& player.chooseUse(Outcome.Benefit, new StringBuilder("Pay ").append(times).append(additionalCost.getText(false)).append(" ?").toString(), ability, game)) {
|
||||
additionalCost.activate();
|
||||
for (Iterator it = ((Costs) additionalCost).iterator(); it.hasNext();) {
|
||||
for (Iterator it = ((Costs) additionalCost).iterator(); it.hasNext(); ) {
|
||||
Cost cost = (Cost) it.next();
|
||||
if (cost instanceof ManaCostsImpl) {
|
||||
ability.getManaCostsToPay().add((ManaCostsImpl) cost.copy());
|
||||
|
|
@ -170,7 +165,7 @@ class ReplicateTriggeredAbility extends TriggeredAbilityImpl {
|
|||
if (card != null) {
|
||||
for (Ability ability : card.getAbilities(game)) {
|
||||
if (ability instanceof ReplicateAbility) {
|
||||
if (((ReplicateAbility) ability).isActivated()) {
|
||||
if (ability.isActivated()) {
|
||||
for (Effect effect : this.getEffects()) {
|
||||
effect.setValue("ReplicateSpell", spell);
|
||||
effect.setValue("ReplicateCount", ((ReplicateAbility) ability).getActivateCount());
|
||||
|
|
@ -213,7 +208,7 @@ class ReplicateCopyEffect extends OneShotEffect {
|
|||
if (card != null) {
|
||||
for (Ability ability : card.getAbilities(game)) {
|
||||
if (ability instanceof ReplicateAbility) {
|
||||
if (((ReplicateAbility) ability).isActivated()) {
|
||||
if (ability.isActivated()) {
|
||||
((ReplicateAbility) ability).resetReplicate();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@ import mage.abilities.Abilities;
|
|||
import mage.abilities.AbilitiesImpl;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.SpellAbility;
|
||||
import mage.constants.*;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.Zone;
|
||||
import mage.game.Game;
|
||||
|
||||
import java.util.List;
|
||||
|
|
@ -26,7 +27,7 @@ public abstract class AdventureCard extends CardImpl {
|
|||
public AdventureCard(AdventureCard card) {
|
||||
super(card);
|
||||
this.spellCard = card.getSpellCard().copy();
|
||||
((AdventureCardSpell)this.spellCard).setParentCard(this);
|
||||
((AdventureCardSpell) this.spellCard).setParentCard(this);
|
||||
}
|
||||
|
||||
public Card getSpellCard() {
|
||||
|
|
@ -91,6 +92,11 @@ public abstract class AdventureCard extends CardImpl {
|
|||
return allAbilities;
|
||||
}
|
||||
|
||||
public Abilities<Ability> getSharedAbilities() {
|
||||
// abilities without spellcard
|
||||
return super.getAbilities();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOwnerId(UUID ownerId) {
|
||||
super.setOwnerId(ownerId);
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ import java.util.List;
|
|||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author phulin
|
||||
*/
|
||||
public class AdventureCardSpellImpl extends CardImpl implements AdventureCardSpell {
|
||||
|
|
@ -112,7 +111,7 @@ class AdventureCardSpellAbility extends SpellAbility {
|
|||
public ActivationStatus canActivate(UUID playerId, Game game) {
|
||||
ExileZone adventureExileZone = game.getExile().getExileZone(ExileAdventureSpellEffect.adventureExileId(playerId, game));
|
||||
Card spellCard = game.getCard(this.getSourceId());
|
||||
if (spellCard != null && spellCard instanceof AdventureCardSpell) {
|
||||
if (spellCard instanceof AdventureCardSpell) {
|
||||
Card card = ((AdventureCardSpell) spellCard).getParentCard();
|
||||
if (adventureExileZone != null && adventureExileZone.contains(card.getId())) {
|
||||
return ActivationStatus.getFalse();
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package mage.constants;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author North
|
||||
*/
|
||||
public enum AsThoughEffectType {
|
||||
|
|
@ -19,15 +18,29 @@ public enum AsThoughEffectType {
|
|||
BLOCK_FORESTWALK,
|
||||
DAMAGE_NOT_BLOCKED,
|
||||
BE_BLOCKED,
|
||||
PLAY_FROM_NOT_OWN_HAND_ZONE, // do not use dialogs in "applies" method for that type of effect (it calls multiple times)
|
||||
|
||||
// PLAY_FROM_NOT_OWN_HAND_ZONE + CAST_AS_INSTANT:
|
||||
// 1. Do not use dialogs in "applies" method for that type of effect (it calls multiple times and will freeze the game)
|
||||
// 2. All effects in "applies" must checks affectedControllerId.equals(source.getControllerId()) (if not then all players will be able to play it)
|
||||
// 3. Target points to mainCard, but card's characteristics from objectId (split, adventure)
|
||||
// TODO: search all PLAY_FROM_NOT_OWN_HAND_ZONE and CAST_AS_INSTANT effects and add support of mainCard and objectId
|
||||
PLAY_FROM_NOT_OWN_HAND_ZONE,
|
||||
CAST_AS_INSTANT,
|
||||
|
||||
ACTIVATE_AS_INSTANT,
|
||||
DAMAGE,
|
||||
SHROUD,
|
||||
HEXPROOF,
|
||||
PAY_0_ECHO,
|
||||
LOOK_AT_FACE_DOWN,
|
||||
|
||||
// SPEND_OTHER_MANA:
|
||||
// 1. It's uses for mana calcs at any zone, not stack only
|
||||
// 2. Compare zone change counter as "objectZCC <= targetZCC + 1"
|
||||
// 3. Compare zone with original (like exiled) and stack, not stack only
|
||||
// TODO: search all SPEND_ONLY_MANA effects and improve counters compare as SPEND_OTHER_MANA
|
||||
SPEND_OTHER_MANA,
|
||||
|
||||
SPEND_ONLY_MANA,
|
||||
TARGET
|
||||
}
|
||||
|
|
|
|||
|
|
@ -110,6 +110,7 @@ public enum SubType {
|
|||
// D
|
||||
DATHOMIRIAN("Dathomirian", SubTypeSet.CreatureType, true), // Star Wars
|
||||
DAUTHI("Dauthi", SubTypeSet.CreatureType),
|
||||
DEMIGOD("Demigod", SubTypeSet.CreatureType),
|
||||
DEMON("Demon", SubTypeSet.CreatureType),
|
||||
DESERTER("Deserter", SubTypeSet.CreatureType),
|
||||
DEVIL("Devil", SubTypeSet.CreatureType),
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -159,9 +159,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;
|
||||
|
|
@ -270,9 +271,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();
|
||||
|
||||
|
|
@ -339,9 +341,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();
|
||||
|
|
@ -416,9 +421,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();
|
||||
|
||||
|
|
@ -443,9 +447,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();
|
||||
}
|
||||
|
|
@ -1060,26 +1062,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;
|
||||
|
|
@ -1143,11 +1152,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 {
|
||||
|
|
@ -1161,7 +1172,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);
|
||||
|
|
@ -3059,10 +3071,16 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
if (available == null) {
|
||||
return true;
|
||||
}
|
||||
MageObjectReference permittingObject = game.getContinuousEffects().asThough(ability.getSourceId(),
|
||||
AsThoughEffectType.SPEND_OTHER_MANA, ability, ability.getControllerId(), game);
|
||||
MageObjectReference permittingObject = game.getContinuousEffects().asThough(copy.getSourceId(),
|
||||
AsThoughEffectType.SPEND_OTHER_MANA, copy, copy.getControllerId(), game);
|
||||
for (Mana mana : abilityOptions) {
|
||||
for (Mana avail : available) {
|
||||
// TODO: SPEND_OTHER_MANA effects with getAsThoughManaType can change mana type to pay,
|
||||
// but that code processing it as any color, need to test and fix another use cases
|
||||
// (example: Sunglasses of Urza - may spend white mana as though it were red mana)
|
||||
|
||||
//
|
||||
// add tests for non any color like Sunglasses of Urza
|
||||
if (permittingObject != null && mana.count() <= avail.count()) {
|
||||
return true;
|
||||
}
|
||||
|
|
@ -3074,14 +3092,35 @@ 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(ability, ability.getSourceId(), playerId, game)) {
|
||||
if (objectAbility.getCosts().canPay(copy, copy.getSourceId(), playerId, game)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return canPlayCardByAlternateCost(game.getCard(ability.getSourceId()), 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(game.getCard(ability.getSourceId()), available, copy, game);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
@ -3202,35 +3241,85 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
}
|
||||
}
|
||||
|
||||
private List<Ability> cardPlayableAbilities(Game game, Card card, boolean setControllerId) {
|
||||
List<Ability> playable = new ArrayList();
|
||||
if (card != null) {
|
||||
for (ActivatedAbility ability : card.getAbilities().getActivatedAbilities(Zone.HAND)) {
|
||||
if (!ability.canActivate(playerId, game).canActivate()) {
|
||||
continue;
|
||||
}
|
||||
private void getPlayableFromNonHandCardAll(Game game, Zone fromZone, Card card, ManaOptions availableMana, List<Ability> output) {
|
||||
if (fromZone == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
UUID savedControllerId = null;
|
||||
if (setControllerId) {
|
||||
// For when owner != caster, e.g. with Psychic Intrusion and similar effects.
|
||||
savedControllerId = getId();
|
||||
ability.setControllerId(getId());
|
||||
}
|
||||
if (ability instanceof SpellAbility
|
||||
&& null != game.getContinuousEffects().asThough(card.getId(),
|
||||
AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, ability, getId(), game)) {
|
||||
playable.add(ability);
|
||||
} else if (ability instanceof PlayLandAbility
|
||||
&& null != game.getContinuousEffects().asThough(card.getId(),
|
||||
AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, card.getSpellAbility(), getId(), game)) {
|
||||
playable.add(ability);
|
||||
}
|
||||
if (setControllerId) {
|
||||
ability.setControllerId(savedControllerId);
|
||||
}
|
||||
// BASIC abilities
|
||||
if (card instanceof SplitCard) {
|
||||
SplitCard splitCard = (SplitCard) card;
|
||||
getPlayableFromNonHandCardSingle(game, fromZone, splitCard.getLeftHalfCard(), splitCard.getLeftHalfCard().getAbilities(), availableMana, output);
|
||||
getPlayableFromNonHandCardSingle(game, fromZone, splitCard.getRightHalfCard(), splitCard.getRightHalfCard().getAbilities(), availableMana, output);
|
||||
getPlayableFromNonHandCardSingle(game, fromZone, splitCard, splitCard.getSharedAbilities(), availableMana, output);
|
||||
} else if (card instanceof AdventureCard) {
|
||||
// adventure must use different card characteristics for different spells (main or adventure)
|
||||
AdventureCard adventureCard = (AdventureCard) card;
|
||||
getPlayableFromNonHandCardSingle(game, fromZone, adventureCard.getSpellCard(), adventureCard.getSpellCard().getAbilities(), availableMana, output);
|
||||
getPlayableFromNonHandCardSingle(game, fromZone, adventureCard, adventureCard.getSharedAbilities(), availableMana, output);
|
||||
} else {
|
||||
getPlayableFromNonHandCardSingle(game, fromZone, card, card.getAbilities(), availableMana, output);
|
||||
}
|
||||
|
||||
// DYNAMIC ADDED abilities
|
||||
if (fromZone != Zone.ALL) { // TODO: test revealed cards with dynamic added abilities
|
||||
// Other activated abilities (added dynamic by effects)
|
||||
LinkedHashMap<UUID, ActivatedAbility> useable;
|
||||
if (card instanceof AdventureCard) {
|
||||
// adventure cards (contains two different cards: main and adventure spell)
|
||||
useable = new LinkedHashMap<>();
|
||||
getOtherUseableActivatedAbilities(((AdventureCard) card).getSpellCard(), fromZone, game, useable);
|
||||
output.addAll(useable.values());
|
||||
|
||||
useable = new LinkedHashMap<>();
|
||||
getOtherUseableActivatedAbilities(card, fromZone, game, useable);
|
||||
output.addAll(useable.values());
|
||||
} else {
|
||||
// all other cards (TODO: check split cards with dynamic added abilities)
|
||||
useable = new LinkedHashMap<>();
|
||||
getOtherUseableActivatedAbilities(card, fromZone, game, useable);
|
||||
output.addAll(useable.values());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void getPlayableFromNonHandCardSingle(Game game, Zone fromZone, Card card, Abilities<Ability> candidateAbilities, ManaOptions availableMana, List<Ability> output) {
|
||||
|
||||
// check "can play from hand" condition as original controller (effects checks affected controller with source controller)
|
||||
// TODO: remove card.getSpellAbility() ?
|
||||
MageObjectReference permittingObject = game.getContinuousEffects().asThough(card.getId(),
|
||||
AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, card.getSpellAbility(), this.getId(), game);
|
||||
boolean canActivateAsHandZone = permittingObject != null
|
||||
|| (fromZone == Zone.GRAVEYARD && canPlayCardsFromGraveyard());
|
||||
|
||||
// check "can play" condition as affected controller
|
||||
for (ActivatedAbility ability : candidateAbilities.getActivatedAbilities(Zone.ALL)) {
|
||||
UUID savedControllerId = ability.getControllerId();
|
||||
ability.setControllerId(this.getId());
|
||||
try {
|
||||
boolean possibleToPlay = false;
|
||||
|
||||
// spell/hand abilities (play from all zones)
|
||||
// need permitingObject or canPlayCardsFromGraveyard
|
||||
if (canActivateAsHandZone
|
||||
&& ability.getZone().match(Zone.HAND)
|
||||
&& (ability instanceof SpellAbility || ability instanceof PlayLandAbility)) {
|
||||
possibleToPlay = true;
|
||||
}
|
||||
|
||||
// zone's abilities (play from specific zone)
|
||||
// no need in permitingObject
|
||||
if (fromZone != Zone.ALL && ability.getZone().match(fromZone)) {
|
||||
possibleToPlay = true;
|
||||
}
|
||||
|
||||
if (possibleToPlay && canPlay(ability, availableMana, card, game)) {
|
||||
output.add(ability);
|
||||
}
|
||||
} finally {
|
||||
ability.setControllerId(savedControllerId);
|
||||
}
|
||||
}
|
||||
return playable;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -3249,11 +3338,9 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
}
|
||||
|
||||
boolean fromAll = fromZone.equals(Zone.ALL);
|
||||
Collection<Card> cards;
|
||||
|
||||
if (hidden && (fromAll || fromZone == Zone.HAND)) {
|
||||
cards = hideDuplicatedAbilities ? hand.getUniqueCards(game) : hand.getCards(game);
|
||||
for (Card card : cards) {
|
||||
for (Card card : hand.getCards(game)) {
|
||||
for (Ability ability : card.getAbilities(game)) { // gets this activated ability from hand? (Morph?)
|
||||
if (ability.getZone().match(Zone.HAND)) {
|
||||
if (ability instanceof ActivatedAbility) {
|
||||
|
|
@ -3284,37 +3371,15 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
}
|
||||
|
||||
if (fromAll || fromZone == Zone.GRAVEYARD) {
|
||||
cards = hideDuplicatedAbilities ? graveyard.getUniqueCards(game) : graveyard.getCards(game);
|
||||
for (Card card : cards) {
|
||||
// Handle split cards in graveyard to support Aftermath
|
||||
if (card instanceof SplitCard) {
|
||||
SplitCard splitCard = (SplitCard) card;
|
||||
getPlayableFromGraveyardCard(game, splitCard.getLeftHalfCard(),
|
||||
splitCard.getLeftHalfCard().getAbilities(), availableMana, playable);
|
||||
getPlayableFromGraveyardCard(game, splitCard.getRightHalfCard(),
|
||||
splitCard.getRightHalfCard().getAbilities(), availableMana, playable);
|
||||
getPlayableFromGraveyardCard(game, splitCard, splitCard.getSharedAbilities(),
|
||||
availableMana, playable);
|
||||
} else if (card instanceof AdventureCard) {
|
||||
AdventureCard adventureCard = (AdventureCard) card;
|
||||
getPlayableFromGraveyardCard(game, adventureCard.getSpellCard(),
|
||||
adventureCard.getSpellCard().getAbilities(), availableMana, playable);
|
||||
getPlayableFromGraveyardCard(game, adventureCard, adventureCard.getAbilities(), availableMana, playable);
|
||||
} else {
|
||||
getPlayableFromGraveyardCard(game, card, card.getAbilities(), availableMana, playable);
|
||||
}
|
||||
|
||||
// Other activated abilities
|
||||
LinkedHashMap<UUID, ActivatedAbility> useable = new LinkedHashMap<>();
|
||||
getOtherUseableActivatedAbilities(card, Zone.GRAVEYARD, game, useable);
|
||||
playable.addAll(useable.values());
|
||||
for (Card card : graveyard.getCards(game)) {
|
||||
getPlayableFromNonHandCardAll(game, Zone.GRAVEYARD, card, availableMana, playable);
|
||||
}
|
||||
}
|
||||
|
||||
if (fromAll || fromZone == Zone.EXILED) {
|
||||
for (ExileZone exile : game.getExile().getExileZones()) {
|
||||
for (Card card : exile.getCards(game)) {
|
||||
playable.addAll(cardPlayableAbilities(game, card, true));
|
||||
getPlayableFromNonHandCardAll(game, Zone.EXILED, card, availableMana, playable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3323,7 +3388,8 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
if (fromAll) {
|
||||
for (Cards revealedCards : game.getState().getRevealed().values()) {
|
||||
for (Card card : revealedCards.getCards(game)) {
|
||||
playable.addAll(cardPlayableAbilities(game, card, false));
|
||||
// revealed cards can be from any zones
|
||||
getPlayableFromNonHandCardAll(game, game.getState().getZone(card.getId()), card, availableMana, playable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3335,7 +3401,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
if (player != null) {
|
||||
if (/*player.isTopCardRevealed() &&*/player.getLibrary().hasCards()) {
|
||||
Card card = player.getLibrary().getFromTop(game);
|
||||
playable.addAll(cardPlayableAbilities(game, card, false));
|
||||
getPlayableFromNonHandCardAll(game, Zone.LIBRARY, card, availableMana, playable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue