Merge branch 'master' into omniscience-fix

This commit is contained in:
Oleg Agafonov 2020-11-01 10:14:50 +01:00 committed by GitHub
commit b3cc1f49a7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
389 changed files with 9467 additions and 5524 deletions

View file

@ -1,25 +1,25 @@
package mage;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import mage.abilities.Abilities;
import mage.abilities.Ability;
import mage.abilities.costs.mana.ManaCost;
import mage.abilities.costs.mana.ManaCosts;
import mage.abilities.keyword.ChangelingAbility;
import mage.abilities.text.TextPart;
import mage.cards.Card;
import mage.cards.FrameStyle;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.SubTypeSet;
import mage.constants.SuperType;
import mage.game.Game;
import mage.game.events.ZoneChangeEvent;
import mage.util.SubTypeList;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
public interface MageObject extends MageItem, Serializable {
String getName();
@ -126,6 +126,10 @@ public interface MageObject extends MageItem, Serializable {
return getCardType().contains(CardType.PLANESWALKER);
}
default boolean isTribal() {
return getCardType().contains(CardType.TRIBAL);
}
default boolean isPermanent() {
return isCreature() || isArtifact() || isPlaneswalker() || isEnchantment() || isLand();
}
@ -139,6 +143,9 @@ public interface MageObject extends MageItem, Serializable {
}
default void addSuperType(SuperType superType) {
if (getSuperType().contains(superType)) {
return;
}
getSuperType().add(superType);
}
@ -151,9 +158,31 @@ public interface MageObject extends MageItem, Serializable {
}
default void addCardType(CardType cardType) {
if (getCardType().contains(cardType)) {
return;
}
getCardType().add(cardType);
}
default void addSubType(Game game, SubType... subTypes) {
for (SubType subType : subTypes) {
if (subType.canGain(this)
&& !hasSubtype(subType, game)) {
getSubtype(game).add(subType);
}
}
}
default void removeAllSubTypes(Game game) {
getSubtype(game).clear();
setIsAllCreatureTypes(false);
}
default void removeAllCreatureTypes(Game game) {
getSubtype(game).removeAll(SubType.getCreatureTypes());
setIsAllCreatureTypes(false);
}
/**
* Checks whether two cards share card types.
*
@ -180,27 +209,36 @@ public interface MageObject extends MageItem, Serializable {
return false;
}
default boolean shareSubtypes(Card otherCard, Game game) {
if (otherCard == null) {
throw new IllegalArgumentException("Params can't be null");
default boolean shareCreatureTypes(Card otherCard, Game game) {
if (!isCreature() && !isTribal()) {
return false;
}
if (this.isCreature() && otherCard.isCreature()) {
if (this.hasAbility(ChangelingAbility.getInstance(), game)
|| this.isAllCreatureTypes()
|| otherCard.hasAbility(ChangelingAbility.getInstance(), game)
|| otherCard.isAllCreatureTypes()) {
return true;
}
if (!otherCard.isCreature() && !otherCard.isTribal()) {
return false;
}
for (SubType subtype : this.getSubtype(game)) {
if (otherCard.hasSubtype(subtype, game)) {
return true;
}
boolean isAllA = this.isAllCreatureTypes();
boolean isAnyA = isAllA || this.getSubtype(game)
.stream()
.map(SubType::getSubTypeSet)
.anyMatch(SubTypeSet.CreatureType::equals);
boolean isAllB = otherCard.isAllCreatureTypes();
boolean isAnyB = isAllB || otherCard
.getSubtype(game)
.stream()
.map(SubType::getSubTypeSet)
.anyMatch(SubTypeSet.CreatureType::equals);
if (!isAnyA || !isAnyB) {
return false;
}
return false;
if (isAllA) {
return isAllB || isAnyB;
}
return isAnyA
&& (isAllB || this
.getSubtype(game)
.stream()
.filter(subType -> subType.getSubTypeSet() == SubTypeSet.CreatureType)
.anyMatch(subType -> otherCard.hasSubtype(subType, game)));
}
boolean isAllCreatureTypes();

View file

@ -1,7 +1,5 @@
package mage;
import java.util.*;
import mage.abilities.Abilities;
import mage.abilities.AbilitiesImpl;
import mage.abilities.Ability;
@ -11,7 +9,6 @@ import mage.abilities.costs.mana.ManaCosts;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.Effect;
import mage.abilities.keyword.ChangelingAbility;
import mage.abilities.mana.ActivatedManaAbilityImpl;
import mage.abilities.text.TextPart;
import mage.abilities.text.TextPartSubType;
@ -20,10 +17,11 @@ import mage.cards.mock.MockCard;
import mage.constants.*;
import mage.game.Game;
import mage.game.events.ZoneChangeEvent;
import mage.game.permanent.Permanent;
import mage.util.GameLog;
import mage.util.SubTypeList;
import java.util.*;
public abstract class MageObjectImpl implements MageObject {
protected UUID objectId;
@ -230,25 +228,10 @@ public abstract class MageObjectImpl implements MageObject {
if (value == null) {
return false;
}
SubTypeList subtypes = this.getSubtype(game);
if (subtypes.contains(value)) {
if (value.getSubTypeSet() == SubTypeSet.CreatureType && isAllCreatureTypes()) {
return true;
} else {
// checking for Changeling
// first make sure input parameter is a creature subtype
// if not, then ChangelingAbility doesn't matter
if (value.getSubTypeSet() != SubTypeSet.CreatureType) {
return false;
}
// as it is a creature subtype, then check the existence of Changeling
Abilities<Ability> checkList;
if (this instanceof Permanent) {
checkList = ((Permanent) this).getAbilities(game);
} else {
checkList = abilities;
}
return checkList.contains(ChangelingAbility.getInstance()) || isAllCreatureTypes();
}
return getSubtype(game).contains(value);
}
@Override
@ -289,7 +272,7 @@ public abstract class MageObjectImpl implements MageObject {
@Override
public void setIsAllCreatureTypes(boolean value) {
isAllCreatureTypes = value;
isAllCreatureTypes = value && (this.isTribal() || this.isCreature());
}
@Override

View file

@ -96,7 +96,7 @@ class KinshipBaseEffect extends OneShotEffect {
if (card != null) {
Cards cards = new CardsImpl(card);
controller.lookAtCards(sourcePermanent.getName(), cards, game);
if (sourcePermanent.shareSubtypes(card, game)) {
if (sourcePermanent.shareCreatureTypes(card, game)) {
if (controller.chooseUse(outcome, new StringBuilder("Kinship - Reveal ").append(card.getLogName()).append('?').toString(), source, game)) {
controller.revealCards(sourcePermanent.getName(), cards, game);
for (Effect effect : kinshipEffects) {

View file

@ -7,10 +7,7 @@ import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import static mage.constants.CardType.CREATURE;
/**
*
* @author htrajan
*/
public class AttachedToCreatureSourceTriggeredAbility extends TriggeredAbilityImpl {
@ -33,7 +30,7 @@ public class AttachedToCreatureSourceTriggeredAbility extends TriggeredAbilityIm
@Override
public boolean checkTrigger(GameEvent event, Game game) {
Permanent attachedPermanent = game.getPermanent(event.getTargetId());
return attachedPermanent != null && attachedPermanent.getCardType().contains(CREATURE);
return attachedPermanent != null && attachedPermanent.isCreature();
}
@Override

View file

@ -1,8 +1,5 @@
package mage.abilities.common;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.ActivatedAbilityImpl;
import mage.abilities.SpecialAction;
@ -15,22 +12,18 @@ import mage.abilities.effects.common.AttachEffect;
import mage.abilities.effects.common.CreateSpecialActionEffect;
import mage.abilities.effects.common.RemoveSpecialActionEffect;
import mage.abilities.keyword.EnchantAbility;
import mage.constants.CardType;
import mage.constants.DependencyType;
import mage.constants.Duration;
import mage.constants.Layer;
import mage.constants.Outcome;
import mage.constants.SubLayer;
import mage.constants.SubType;
import mage.constants.Zone;
import mage.constants.*;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.target.Target;
import mage.target.common.TargetCreaturePermanent;
import mage.target.targetpointer.FixedTarget;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
*
* @author emerald000
*/
public class LicidAbility extends ActivatedAbilityImpl {
@ -116,8 +109,8 @@ class LicidContinuousEffect extends ContinuousEffectImpl {
case TypeChangingEffects_4:
licid.getCardType().clear();
licid.addCardType(CardType.ENCHANTMENT);
licid.getSubtype(game).clear();
licid.getSubtype(game).add(SubType.AURA);
licid.removeAllSubTypes(game);
licid.addSubType(game, SubType.AURA);
break;
case AbilityAddingRemovingEffects_6:
List<Ability> toRemove = new ArrayList<>();

View file

@ -1,4 +1,3 @@
package mage.abilities.common;
import mage.abilities.TriggeredAbilityImpl;
@ -11,7 +10,6 @@ import mage.game.stack.Spell;
import mage.target.targetpointer.FixedTarget;
/**
*
* @author North
*/
public class SpellCastControllerTriggeredAbility extends TriggeredAbilityImpl {
@ -67,7 +65,7 @@ public class SpellCastControllerTriggeredAbility extends TriggeredAbilityImpl {
Spell spell = game.getStack().getSpell(event.getTargetId());
if (spell != null && filter.match(spell, getSourceId(), getControllerId(), game)) {
if (rememberSource) {
this.getEffects().get(0).setTargetPointer(new FixedTarget(spell.getId()));
this.getEffects().get(0).setTargetPointer(new FixedTarget(spell.getId(), game));
}
return true;
}

View file

@ -3,13 +3,13 @@ package mage.abilities.effects;
import mage.abilities.Ability;
import mage.abilities.ActivatedAbility;
import mage.cards.Card;
import mage.cards.ModalDoubleFacesCard;
import mage.cards.SplitCard;
import mage.constants.*;
import mage.game.Game;
import mage.players.Player;
import java.util.UUID;
import mage.cards.SplitCard;
import mage.cards.SplitCardHalf;
import mage.players.Player;
/**
* @author BetaSteward_at_googlemail.com
@ -22,7 +22,7 @@ public abstract class AsThoughEffectImpl extends ContinuousEffectImpl implements
public AsThoughEffectImpl(AsThoughEffectType type, Duration duration, Outcome outcome) {
this(type, duration, outcome, false);
}
public AsThoughEffectImpl(AsThoughEffectType type, Duration duration, Outcome outcome, boolean consumable) {
super(duration, outcome);
this.type = type;
@ -74,12 +74,12 @@ public abstract class AsThoughEffectImpl extends ContinuousEffectImpl implements
* Internal method to do the neccessary to allow the card from objectId to be cast or played (if it's a land) without paying any mana.
* Additional costs (like sacrificing or discarding) have still to be payed.
* Checks if the card is of the correct type or in the correct zone have to be done before.
*
* @param objectId sourceId of the card to play
* @param source source ability that allows this effect
*
* @param objectId sourceId of the card to play
* @param source source ability that allows this effect
* @param affectedControllerId player allowed to play the card
* @param game
* @return
* @return
*/
protected boolean allowCardToPlayWithoutMana(UUID objectId, Ability source, UUID affectedControllerId, Game game) {
Player player = game.getPlayer(affectedControllerId);
@ -89,9 +89,14 @@ public abstract class AsThoughEffectImpl extends ContinuousEffectImpl implements
}
if (!card.isLand()) {
if (card instanceof SplitCard) {
SplitCardHalf leftCard = ((SplitCard) card).getLeftHalfCard();
Card leftCard = ((SplitCard) card).getLeftHalfCard();
player.setCastSourceIdWithAlternateMana(leftCard.getId(), null, leftCard.getSpellAbility().getCosts());
SplitCardHalf rightCard = ((SplitCard) card).getRightHalfCard();
Card rightCard = ((SplitCard) card).getRightHalfCard();
player.setCastSourceIdWithAlternateMana(rightCard.getId(), null, rightCard.getSpellAbility().getCosts());
} else if (card instanceof ModalDoubleFacesCard) {
Card leftCard = ((ModalDoubleFacesCard) card).getLeftHalfCard();
player.setCastSourceIdWithAlternateMana(leftCard.getId(), null, leftCard.getSpellAbility().getCosts());
Card rightCard = ((ModalDoubleFacesCard) card).getRightHalfCard();
player.setCastSourceIdWithAlternateMana(rightCard.getId(), null, rightCard.getSpellAbility().getCosts());
} else {
player.setCastSourceIdWithAlternateMana(objectId, null, card.getSpellAbility().getCosts());

View file

@ -1,5 +1,6 @@
package mage.abilities.effects;
import mage.ApprovingObject;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.MageSingleton;
@ -7,6 +8,8 @@ import mage.abilities.StaticAbility;
import mage.abilities.effects.common.continuous.BecomesFaceDownCreatureEffect;
import mage.abilities.effects.common.continuous.CommanderReplacementEffect;
import mage.cards.*;
import mage.choices.Choice;
import mage.choices.ChoiceImpl;
import mage.constants.*;
import mage.filter.FilterCard;
import mage.filter.predicate.Predicate;
@ -29,9 +32,6 @@ import java.io.Serializable;
import java.util.*;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import mage.ApprovingObject;
import mage.choices.Choice;
import mage.choices.ChoiceImpl;
/**
* @author BetaSteward_at_googlemail.com
@ -334,7 +334,7 @@ public class ContinuousEffects implements Serializable {
* @param event
* @param game
* @return a list of all {@link ReplacementEffect} that apply to the current
* event
* event
*/
private Map<ReplacementEffect, Set<Ability>> getApplicableReplacementEffects(GameEvent event, Game game) {
Map<ReplacementEffect, Set<Ability>> replaceEffects = new HashMap<>();
@ -343,7 +343,7 @@ public class ContinuousEffects implements Serializable {
}
// boolean checkLKI = event.getType().equals(EventType.ZONE_CHANGE) || event.getType().equals(EventType.DESTROYED_PERMANENT);
//get all applicable transient Replacement effects
for (Iterator<ReplacementEffect> iterator = replacementEffects.iterator(); iterator.hasNext();) {
for (Iterator<ReplacementEffect> iterator = replacementEffects.iterator(); iterator.hasNext(); ) {
ReplacementEffect effect = iterator.next();
if (!effect.checksEventType(event, game)) {
continue;
@ -376,7 +376,7 @@ public class ContinuousEffects implements Serializable {
}
}
for (Iterator<PreventionEffect> iterator = preventionEffects.iterator(); iterator.hasNext();) {
for (Iterator<PreventionEffect> iterator = preventionEffects.iterator(); iterator.hasNext(); ) {
PreventionEffect effect = iterator.next();
if (!effect.checksEventType(event, game)) {
continue;
@ -507,7 +507,7 @@ public class ContinuousEffects implements Serializable {
* @param controllerId
* @param game
* @return sourceId of the permitting effect if any exists otherwise returns
* null
* null
*/
public ApprovingObject asThough(UUID objectId, AsThoughEffectType type, Ability affectedAbility, UUID controllerId, Game game) {
List<AsThoughEffect> asThoughEffectsList = getApplicableAsThoughEffects(type, game);
@ -515,6 +515,8 @@ 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 ModalDoubleFacesCardHalf) {
idToCheck = ((ModalDoubleFacesCardHalf) affectedAbility.getSourceObject(game)).getParentCard().getId();
} else if (affectedAbility != null && affectedAbility.getSourceObject(game) instanceof AdventureCardSpell
&& !type.needPlayCardAbility()) {
// adventure spell uses alternative characteristics for spell/stack
@ -523,6 +525,8 @@ public class ContinuousEffects implements Serializable {
Card card = game.getCard(objectId);
if (card instanceof SplitCardHalf) {
idToCheck = ((SplitCardHalf) card).getParentCard().getId();
} else if (card instanceof ModalDoubleFacesCardHalf) {
idToCheck = ((ModalDoubleFacesCardHalf) card).getParentCard().getId();
} else if (card instanceof AdventureCardSpell
&& !type.needPlayCardAbility()) {
// adventure spell uses alternative characteristics for spell/stack
@ -568,24 +572,24 @@ public class ContinuousEffects implements Serializable {
} else if (possibleApprovingObjects.size() > 1) {
// Select the ability that you use to permit the action
Map<String, String> keyChoices = new HashMap<>();
for(ApprovingObject approvingObject :possibleApprovingObjects) {
MageObject mageObject = game.getObject(approvingObject.getApprovingAbility().getSourceId());
keyChoices.put(approvingObject.getApprovingAbility().getId().toString(),
for (ApprovingObject approvingObject : possibleApprovingObjects) {
MageObject mageObject = game.getObject(approvingObject.getApprovingAbility().getSourceId());
keyChoices.put(approvingObject.getApprovingAbility().getId().toString(),
(approvingObject.getApprovingAbility().getRule(mageObject == null ? "" : mageObject.getName()))
+ (mageObject == null ? "" : " (" + mageObject.getIdName() + ")"));
+ (mageObject == null ? "" : " (" + mageObject.getIdName() + ")"));
}
Choice choicePermitting = new ChoiceImpl(true);
choicePermitting.setMessage("Choose the permitting object");
choicePermitting.setKeyChoices(keyChoices);
Player player = game.getPlayer(controllerId);
player.choose(Outcome.Detriment, choicePermitting, game);
for(ApprovingObject approvingObject: possibleApprovingObjects) {
for (ApprovingObject approvingObject : possibleApprovingObjects) {
if (approvingObject.getApprovingAbility().getId().toString().equals(choicePermitting.getChoiceKey())) {
return approvingObject;
}
}
}
}
return null;
@ -833,7 +837,7 @@ public class ContinuousEffects implements Serializable {
do {
Map<ReplacementEffect, Set<Ability>> rEffects = getApplicableReplacementEffects(event, game);
// Remove all consumed effects (ability dependant)
for (Iterator<ReplacementEffect> it1 = rEffects.keySet().iterator(); it1.hasNext();) {
for (Iterator<ReplacementEffect> it1 = rEffects.keySet().iterator(); it1.hasNext(); ) {
ReplacementEffect entry = it1.next();
if (consumed.containsKey(entry.getId()) /*&& !(entry instanceof CommanderReplacementEffect) */) { // 903.9.
Set<UUID> consumedAbilitiesIds = consumed.get(entry.getId());
@ -1018,7 +1022,7 @@ public class ContinuousEffects implements Serializable {
.entrySet()
.stream()
.filter(entry -> dependentTo.contains(entry.getKey().getId())
&& entry.getValue().contains(effect.getId()))
&& entry.getValue().contains(effect.getId()))
.forEach(entry -> {
entry.getValue().remove(effect.getId());
dependentTo.remove(entry.getKey().getId());
@ -1052,7 +1056,7 @@ public class ContinuousEffects implements Serializable {
continue;
}
// check if waiting effects can be applied now
for (Iterator<Map.Entry<ContinuousEffect, Set<UUID>>> iterator = waitingEffects.entrySet().iterator(); iterator.hasNext();) {
for (Iterator<Map.Entry<ContinuousEffect, Set<UUID>>> iterator = waitingEffects.entrySet().iterator(); iterator.hasNext(); ) {
Map.Entry<ContinuousEffect, Set<UUID>> entry = iterator.next();
if (!appliedEffects.containsAll(entry.getValue())) { // all dependent to effects are applied now so apply the effect itself
continue;

View file

@ -103,10 +103,11 @@ public class CopyEffect extends ContinuousEffectImpl {
for (CardType type : copyFromObject.getCardType()) {
permanent.addCardType(type);
}
permanent.getSubtype(game).clear();
for (SubType type : copyFromObject.getSubtype(game)) {
permanent.getSubtype(game).add(type);
}
permanent.removeAllSubTypes(game);
permanent.getSubtype(game).addAll(copyFromObject.getSubtype(game));
permanent.setIsAllCreatureTypes(copyFromObject.isAllCreatureTypes());
permanent.getSuperType().clear();
for (SuperType type : copyFromObject.getSuperType()) {
permanent.addSuperType(type);

View file

@ -31,10 +31,11 @@ public class CopyTokenEffect extends ContinuousEffectImpl {
for (CardType type : token.getCardType()) {
permanent.addCardType(type);
}
permanent.getSubtype(game).clear();
permanent.removeAllSubTypes(game);
for (SubType type : token.getSubtype(game)) {
permanent.getSubtype(game).add(type);
permanent.addSubType(game, type);
}
permanent.setIsAllCreatureTypes(token.isAllCreatureTypes());
permanent.getSuperType().clear();
for (SuperType type : token.getSuperType()) {
permanent.addSuperType(type);

View file

@ -1,10 +1,5 @@
package mage.abilities.effects.common;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import mage.MageObject;
import mage.ObjectColor;
import mage.abilities.Ability;
@ -25,6 +20,11 @@ import mage.util.CardUtil;
import mage.util.functions.ApplyToPermanent;
import mage.util.functions.EmptyApplyToPermanent;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
/**
* @author LevelX2
*/
@ -179,10 +179,10 @@ public class CreateTokenCopyTargetEffect extends OneShotEffect {
token.getSuperType().remove(SuperType.LEGENDARY);
}
if (startingLoyalty!=-1){
if (startingLoyalty != -1) {
token.setStartingLoyalty(startingLoyalty);
}
if (additionalCardType != null && !token.getCardType().contains(additionalCardType)) {
if (additionalCardType != null) {
token.addCardType(additionalCardType);
}
if (hasHaste) {
@ -199,12 +199,12 @@ public class CreateTokenCopyTargetEffect extends OneShotEffect {
token.removePTCDA();
token.getToughness().modifyBaseValue(tokenToughness);
}
if (additionalSubType != null && !token.hasSubtype(additionalSubType, game)) {
token.getSubtype(game).add(additionalSubType);
if (onlySubType != null) {
token.removeAllCreatureTypes(game);
token.addSubType(game, onlySubType);
}
if (onlySubType != null && !token.hasSubtype(onlySubType, game)) {
token.getSubtype(game).clear();
token.getSubtype(game).add(onlySubType);
if (additionalSubType != null && !token.hasSubtype(additionalSubType, game)) {
token.addSubType(game, additionalSubType);
}
if (color != null) {
token.getColor(game).setColor(color);

View file

@ -29,9 +29,7 @@ public class AddCardSubTypeTargetEffect extends ContinuousEffectImpl {
public boolean apply(Game game, Ability source) {
Permanent target = game.getPermanent(targetPointer.getFirst(game, source));
if (target != null) {
if (!target.hasSubtype(addedSubType, game)) {
target.getSubtype(game).add(addedSubType);
}
target.addSubType(game, addedSubType);
} else {
if (duration == Duration.Custom) {
discard();

View file

@ -35,8 +35,8 @@ public class AddCardSubtypeAllEffect extends ContinuousEffectImpl {
@Override
public boolean apply(Game game, Ability source) {
for (Permanent perm : game.getBattlefield().getActivePermanents(filter, source.getControllerId(), source.getSourceId(), game)) {
if (perm != null && !perm.hasSubtype(addedSubtype, game)) {
perm.getSubtype(game).add(addedSubtype);
if (perm != null) {
perm.addSubType(game, addedSubtype);
}
}
return true;

View file

@ -33,8 +33,8 @@ public class AddCardSubtypeAttachedEffect extends ContinuousEffectImpl {
Permanent equipment = game.getPermanent(source.getSourceId());
if (equipment != null && equipment.getAttachedTo() != null) {
Permanent target = game.getPermanent(equipment.getAttachedTo());
if (target != null && !target.hasSubtype(addedSubtype, game))
target.getSubtype(game).add(addedSubtype);
if (target != null)
target.addSubType(game, addedSubtype);
}
return true;
}

View file

@ -33,7 +33,7 @@ public class AddCardTypeAttachedEffect extends ContinuousEffectImpl {
Permanent equipment = game.getPermanent(source.getSourceId());
if (equipment != null && equipment.getAttachedTo() != null) {
Permanent target = game.getPermanent(equipment.getAttachedTo());
if (target != null && !target.getCardType().contains(addedCardType)) {
if (target != null) {
target.addCardType(addedCardType);
}
}

View file

@ -1,8 +1,5 @@
package mage.abilities.effects.common.continuous;
import java.util.ArrayList;
import java.util.Locale;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.Mode;
@ -11,6 +8,9 @@ import mage.constants.*;
import mage.game.Game;
import mage.game.permanent.Permanent;
import java.util.ArrayList;
import java.util.Locale;
/**
* @author emerald000
*/
@ -46,9 +46,7 @@ public class AddCardTypeSourceEffect extends ContinuousEffectImpl {
Permanent permanent = game.getPermanent(source.getSourceId());
if (permanent != null && affectedObjectList.contains(new MageObjectReference(permanent, game))) {
for (CardType cardType : addedCardTypes) {
if (!permanent.getCardType().contains(cardType)) {
permanent.addCardType(cardType);
}
permanent.addCardType(cardType);
}
return true;
} else if (this.getDuration() == Duration.Custom) {

View file

@ -1,20 +1,15 @@
package mage.abilities.effects.common.continuous;
import mage.abilities.Ability;
import mage.abilities.Mode;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.constants.*;
import mage.game.Game;
import mage.game.permanent.Permanent;
import java.util.ArrayList;
import java.util.Locale;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.Mode;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.constants.CardType;
import mage.constants.DependencyType;
import mage.constants.Duration;
import mage.constants.Layer;
import mage.constants.Outcome;
import mage.constants.SubLayer;
import mage.game.Game;
import mage.game.permanent.Permanent;
/**
* @author nantuko
@ -48,9 +43,7 @@ public class AddCardTypeTargetEffect extends ContinuousEffectImpl {
Permanent target = game.getPermanent(targetId);
if (target != null) {
for (CardType cardType : addedCardTypes) {
if (!target.getCardType().contains(cardType)) {
target.addCardType(cardType);
}
target.addCardType(cardType);
}
result = true;
}

View file

@ -23,8 +23,8 @@ public class AddChosenSubtypeEffect extends ContinuousEffectImpl {
Permanent permanent = game.getPermanent(source.getSourceId());
if (permanent != null) {
SubType subType = ChooseCreatureTypeEffect.getChosenCreatureType(permanent.getId(), game);
if (subType != null && !permanent.hasSubtype(subType, game)) {
permanent.getSubtype(game).add(subType);
if (subType != null) {
permanent.addSubType(game, subType);
}
}
return true;

View file

@ -14,7 +14,7 @@ import mage.game.permanent.Permanent;
public class BecomesAllBasicsControlledEffect extends ContinuousEffectImpl {
public BecomesAllBasicsControlledEffect() {
super(Duration.WhileOnBattlefield, Outcome.Detriment);
super(Duration.WhileOnBattlefield, Layer.TypeChangingEffects_4, SubLayer.NA, Outcome.Detriment);
this.staticText = "Lands you control are every basic land type in addition to their other types";
dependencyTypes.add(DependencyType.BecomeMountain);
dependencyTypes.add(DependencyType.BecomeForest);
@ -27,65 +27,29 @@ public class BecomesAllBasicsControlledEffect extends ContinuousEffectImpl {
super(effect);
}
@Override
public boolean apply(Game game, Ability source) {
return false;
}
@Override
public BecomesAllBasicsControlledEffect copy() {
return new BecomesAllBasicsControlledEffect(this);
}
@Override
public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) {
public boolean apply(Game game, Ability source) {
for (Permanent permanent : game.getBattlefield().getActivePermanents(
StaticFilters.FILTER_CONTROLLED_PERMANENT_LAND, source.getControllerId(), game)) {
switch (layer) {
case TypeChangingEffects_4:
if (!permanent.hasSubtype(SubType.SWAMP, game)) {
permanent.getSubtype(game).add(SubType.SWAMP);
}
if (!permanent.hasSubtype(SubType.MOUNTAIN, game)) {
permanent.getSubtype(game).add(SubType.MOUNTAIN);
}
if (!permanent.hasSubtype(SubType.FOREST, game)) {
permanent.getSubtype(game).add(SubType.FOREST);
}
if (!permanent.hasSubtype(SubType.ISLAND, game)) {
permanent.getSubtype(game).add(SubType.ISLAND);
}
if (!permanent.hasSubtype(SubType.PLAINS, game)) {
permanent.getSubtype(game).add(SubType.PLAINS);
}
if (permanent.hasSubtype(SubType.SWAMP, game)
&& !permanent.getAbilities().containsRule(new BlackManaAbility())) {
permanent.addAbility(new BlackManaAbility(), source.getSourceId(), game);
}
if (permanent.hasSubtype(SubType.MOUNTAIN, game)
&& !permanent.getAbilities().containsRule(new RedManaAbility())) {
permanent.addAbility(new RedManaAbility(), source.getSourceId(), game);
}
if (permanent.hasSubtype(SubType.FOREST, game)
&& !permanent.getAbilities().containsRule(new GreenManaAbility())) {
permanent.addAbility(new GreenManaAbility(), source.getSourceId(), game);
}
if (permanent.hasSubtype(SubType.ISLAND, game)
&& !permanent.getAbilities().containsRule(new BlueManaAbility())) {
permanent.addAbility(new BlueManaAbility(), source.getSourceId(), game);
}
if (permanent.hasSubtype(SubType.PLAINS, game)
&& !permanent.getAbilities().containsRule(new WhiteManaAbility())) {
permanent.addAbility(new WhiteManaAbility(), source.getSourceId(), game);
}
break;
}
permanent.addSubType(
game,
SubType.PLAINS,
SubType.ISLAND,
SubType.SWAMP,
SubType.MOUNTAIN,
SubType.FOREST
);
permanent.addAbility(new WhiteManaAbility(), source.getSourceId(), game);
permanent.addAbility(new BlueManaAbility(), source.getSourceId(), game);
permanent.addAbility(new BlackManaAbility(), source.getSourceId(), game);
permanent.addAbility(new RedManaAbility(), source.getSourceId(), game);
permanent.addAbility(new GreenManaAbility(), source.getSourceId(), game);
}
return true;
}
@Override
public boolean hasLayer(Layer layer) {
return layer == Layer.TypeChangingEffects_4;
}
}

View file

@ -5,18 +5,12 @@ import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.abilities.keyword.EnchantAbility;
import mage.constants.DependencyType;
import mage.constants.Duration;
import mage.constants.Layer;
import mage.constants.Outcome;
import mage.constants.SubLayer;
import mage.constants.SubType;
import mage.constants.*;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.target.Target;
/**
*
* @author LevelX2
*/
public class BecomesAuraSourceEffect extends ContinuousEffectImpl implements SourceEffect {
@ -58,9 +52,7 @@ public class BecomesAuraSourceEffect extends ContinuousEffectImpl implements Sou
switch (layer) {
case TypeChangingEffects_4:
if (sublayer == SubLayer.NA) {
if (!permanent.hasSubtype(SubType.AURA, game)) {
permanent.getSubtype(game).add(SubType.AURA);
}
permanent.addSubType(game, SubType.AURA);
}
break;
case AbilityAddingRemovingEffects_6:

View file

@ -16,7 +16,7 @@ public class BecomesBasicLandEnchantedEffect extends ContinuousEffectImpl {
protected List<SubType> landTypes = new ArrayList<>();
public BecomesBasicLandEnchantedEffect(SubType... landNames) {
super(Duration.WhileOnBattlefield, Outcome.Detriment);
super(Duration.WhileOnBattlefield, Layer.TypeChangingEffects_4, SubLayer.NA, Outcome.Detriment);
landTypes.addAll(Arrays.asList(landNames));
this.staticText = setText();
}
@ -26,68 +26,55 @@ public class BecomesBasicLandEnchantedEffect extends ContinuousEffectImpl {
this.landTypes.addAll(effect.landTypes);
}
@Override
public boolean apply(Game game, Ability source) {
return false;
}
@Override
public BecomesBasicLandEnchantedEffect copy() {
return new BecomesBasicLandEnchantedEffect(this);
}
@Override
public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) {
public boolean apply(Game game, Ability source) {
Permanent enchantment = game.getPermanent(source.getSourceId());
if (enchantment != null && enchantment.getAttachedTo() != null) {
Permanent permanent = game.getPermanent(enchantment.getAttachedTo());
if (permanent != null) {
switch (layer) {
case TypeChangingEffects_4:
// lands intrictically have the mana ability associated with their type, so added here in layer 4
permanent.getSubtype(game).removeAll(SubType.getLandTypes());
permanent.getSubtype(game).addAll(landTypes);
permanent.removeAllAbilities(source.getSourceId(), game);
for (SubType landType : landTypes) {
switch (landType) {
case SWAMP:
if (permanent.hasSubtype(SubType.SWAMP, game)) {
permanent.addAbility(new BlackManaAbility(), source.getSourceId(), game);
}
break;
case MOUNTAIN:
if (permanent.hasSubtype(SubType.MOUNTAIN, game)) {
permanent.addAbility(new RedManaAbility(), source.getSourceId(), game);
}
break;
case FOREST:
if (permanent.hasSubtype(SubType.FOREST, game)) {
permanent.addAbility(new GreenManaAbility(), source.getSourceId(), game);
}
break;
case ISLAND:
if (permanent.hasSubtype(SubType.ISLAND, game)) {
permanent.addAbility(new BlueManaAbility(), source.getSourceId(), game);
}
break;
case PLAINS:
if (permanent.hasSubtype(SubType.PLAINS, game)) {
permanent.addAbility(new WhiteManaAbility(), source.getSourceId(), game);
}
break;
}
}
break;
}
return true;
if (enchantment == null || enchantment.getAttachedTo() == null) {
return false;
}
Permanent permanent = game.getPermanent(enchantment.getAttachedTo());
if (permanent == null) {
return false;
}
// lands intrictically have the mana ability associated with their type, so added here in layer 4
permanent.getSubtype(game).removeAll(SubType.getLandTypes());
permanent.getSubtype(game).addAll(landTypes);
permanent.removeAllAbilities(source.getSourceId(), game);
for (SubType landType : landTypes) {
switch (landType) {
case PLAINS:
if (permanent.hasSubtype(SubType.PLAINS, game)) {
permanent.addAbility(new WhiteManaAbility(), source.getSourceId(), game);
}
break;
case ISLAND:
if (permanent.hasSubtype(SubType.ISLAND, game)) {
permanent.addAbility(new BlueManaAbility(), source.getSourceId(), game);
}
break;
case SWAMP:
if (permanent.hasSubtype(SubType.SWAMP, game)) {
permanent.addAbility(new BlackManaAbility(), source.getSourceId(), game);
}
break;
case MOUNTAIN:
if (permanent.hasSubtype(SubType.MOUNTAIN, game)) {
permanent.addAbility(new RedManaAbility(), source.getSourceId(), game);
}
break;
case FOREST:
if (permanent.hasSubtype(SubType.FOREST, game)) {
permanent.addAbility(new GreenManaAbility(), source.getSourceId(), game);
}
break;
}
}
return false;
}
@Override
public boolean hasLayer(Layer layer) {
return layer == Layer.TypeChangingEffects_4;
return true;
}
private String setText() {

View file

@ -123,7 +123,7 @@ public class BecomesBasicLandTargetEffect extends ContinuousEffectImpl {
landTypesToAdd.clear();
for (SubType subtype : landTypes) {
if (!land.hasSubtype(subtype, game)) {
land.getSubtype(game).add(subtype);
land.addSubType(game, subtype);
landTypesToAdd.add(subtype);
}
}

View file

@ -60,14 +60,10 @@ public class BecomesBlackZombieAdditionEffect extends ContinuousEffectImpl {
if (creature != null) {
switch (layer) {
case TypeChangingEffects_4:
if (sublayer == SubLayer.NA) {
if (!creature.hasSubtype(SubType.ZOMBIE, game)) {
creature.getSubtype(game).add(SubType.ZOMBIE);
}
}
creature.addSubType(game, SubType.ZOMBIE);
break;
case ColorChangingEffects_5:
if (sublayer == SubLayer.NA && this.giveBlackColor) {
if (this.giveBlackColor) {
creature.getColor(game).setBlack(true);
}
break;

View file

@ -25,18 +25,18 @@ public class BecomesCreatureAllEffect extends ContinuousEffectImpl {
private boolean loseTypes = false;
protected boolean loseName = false;
public BecomesCreatureAllEffect(Token token, String theyAreStillType,
FilterPermanent filter, Duration duration, boolean loseColor) {
public BecomesCreatureAllEffect(Token token, String theyAreStillType,
FilterPermanent filter, Duration duration, boolean loseColor) {
this(token, theyAreStillType, filter, duration, loseColor, false, false);
}
public BecomesCreatureAllEffect(Token token, String theyAreStillType,
FilterPermanent filter, Duration duration, boolean loseColor, boolean loseName) {
public BecomesCreatureAllEffect(Token token, String theyAreStillType,
FilterPermanent filter, Duration duration, boolean loseColor, boolean loseName) {
this(token, theyAreStillType, filter, duration, loseColor, loseName, false);
}
public BecomesCreatureAllEffect(Token token, String theyAreStillType,
FilterPermanent filter, Duration duration, boolean loseColor, boolean loseName, boolean loseTypes) {
public BecomesCreatureAllEffect(Token token, String theyAreStillType,
FilterPermanent filter, Duration duration, boolean loseColor, boolean loseName, boolean loseTypes) {
super(duration, Outcome.BecomeCreature);
this.token = token;
this.theyAreStillType = theyAreStillType;
@ -44,7 +44,7 @@ public class BecomesCreatureAllEffect extends ContinuousEffectImpl {
this.loseColor = loseColor;
this.loseName = loseName;
this.loseTypes = loseTypes;
this.dependencyTypes.add(DependencyType.BecomeCreature);
}
@ -87,83 +87,67 @@ public class BecomesCreatureAllEffect extends ContinuousEffectImpl {
}
for (Permanent permanent : affectedPermanents) {
if (permanent != null) {
switch (layer) {
case TextChangingEffects_3:
if (sublayer == SubLayer.NA) {
if (loseName) {
permanent.setName(token.getName());
}
if (permanent == null) {
continue;
}
switch (layer) {
case TextChangingEffects_3:
if (loseName) {
permanent.setName(token.getName());
}
break;
case TypeChangingEffects_4:
for (CardType t : token.getCardType()) {
permanent.addCardType(t);
}
if (theyAreStillType != null || loseTypes) {
permanent.removeAllCreatureTypes(game);
}
for (SubType t : token.getSubtype(game)) {
permanent.addSubType(game, t);
}
permanent.setIsAllCreatureTypes(token.isAllCreatureTypes());
for (SuperType t : token.getSuperType()) {
if (!permanent.getSuperType().contains(t)) {
permanent.addSuperType(t);
}
break;
}
case TypeChangingEffects_4:
if (sublayer == SubLayer.NA) {
if (theyAreStillType != null) {
permanent.getSubtype(game).retainAll(SubType.getLandTypes());
permanent.getSubtype(game).addAll(token.getSubtype(game));
} else {
if (loseTypes) {
permanent.getSubtype(game).retainAll(SubType.getLandTypes());
}
break;
for (SubType t : token.getSubtype(game)) {
if (!permanent.hasSubtype(t, game)) {
permanent.getSubtype(game).add(t);
}
}
}
case ColorChangingEffects_5:
if (this.loseColor) {
permanent.getColor(game).setWhite(false);
permanent.getColor(game).setBlue(false);
permanent.getColor(game).setBlack(false);
permanent.getColor(game).setRed(false);
permanent.getColor(game).setGreen(false);
}
if (token.getColor(game).hasColor()) {
permanent.getColor(game).addColor(token.getColor(game));
}
break;
for (SuperType t : token.getSuperType()) {
if (!permanent.getSuperType().contains(t)) {
permanent.addSuperType(t);
}
}
for (CardType t : token.getCardType()) {
if (!permanent.getCardType().contains(t)) {
permanent.addCardType(t);
}
}
case AbilityAddingRemovingEffects_6:
if (!token.getAbilities().isEmpty()) {
for (Ability ability : token.getAbilities()) {
permanent.addAbility(ability, source.getSourceId(), game);
}
break;
}
break;
case ColorChangingEffects_5:
if (sublayer == SubLayer.NA) {
if (this.loseColor) {
permanent.getColor(game).setBlack(false);
permanent.getColor(game).setGreen(false);
permanent.getColor(game).setBlue(false);
permanent.getColor(game).setWhite(false);
permanent.getColor(game).setRed(false);
}
if (token.getColor(game).hasColor()) {
permanent.getColor(game).addColor(token.getColor(game));
}
case PTChangingEffects_7:
if (sublayer == SubLayer.SetPT_7b) {
int power = token.getPower().getValue();
int toughness = token.getToughness().getValue();
if (power != 0 && toughness != 0) {
permanent.getPower().setValue(power);
permanent.getToughness().setValue(toughness);
}
break;
case AbilityAddingRemovingEffects_6:
if (sublayer == SubLayer.NA) {
if (!token.getAbilities().isEmpty()) {
for (Ability ability : token.getAbilities()) {
permanent.addAbility(ability, source.getSourceId(), game);
}
}
}
break;
case PTChangingEffects_7:
if (sublayer == SubLayer.SetPT_7b) {
int power = token.getPower().getValue();
int toughness = token.getToughness().getValue();
if (power != 0 && toughness != 0) {
permanent.getPower().setValue(power);
permanent.getToughness().setValue(toughness);
}
}
break;
}
}
break;
}
}
return true;

View file

@ -78,19 +78,14 @@ public class BecomesCreatureAttachedEffect extends ContinuousEffectImpl {
switch (loseType) {
case ALL:
case ALL_BUT_COLOR:
permanent.removeAllSubTypes(game);
break;
case ABILITIES_SUBTYPE:
if (permanent.isLand()) {
permanent.getSubtype(game).retainAll(SubType.getLandTypes());
} else {
permanent.getSubtype(game).clear();
}
permanent.setIsAllCreatureTypes(false);
permanent.removeAllCreatureTypes(game);
break;
}
for (SubType t : token.getSubtype(game)) {
if (!permanent.hasSubtype(t, game)) {
permanent.getSubtype(game).add(t);
}
permanent.addSubType(game, t);
}
}
break;
@ -98,11 +93,11 @@ public class BecomesCreatureAttachedEffect extends ContinuousEffectImpl {
case ColorChangingEffects_5:
if (sublayer == SubLayer.NA) {
if (loseType == LoseType.ALL || loseType == LoseType.COLOR) {
permanent.getColor(game).setBlack(false);
permanent.getColor(game).setGreen(false);
permanent.getColor(game).setBlue(false);
permanent.getColor(game).setWhite(false);
permanent.getColor(game).setBlue(false);
permanent.getColor(game).setBlack(false);
permanent.getColor(game).setRed(false);
permanent.getColor(game).setGreen(false);
}
if (token.getColor(game).hasColor()) {
permanent.getColor(game).addColor(token.getColor(game));

View file

@ -1,42 +1,33 @@
package mage.abilities.effects.common.continuous;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.constants.*;
import mage.abilities.effects.OneShotEffect;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.game.permanent.token.Token;
import mage.target.targetpointer.FixedTarget;
/**
* @author jeffwadsworth
*/
public class BecomesCreatureAttachedWithActivatedAbilityOrSpellEffect extends ContinuousEffectImpl {
public class BecomesCreatureAttachedWithActivatedAbilityOrSpellEffect extends OneShotEffect {
public enum LoseType {
NONE, ALL, ALL_BUT_COLOR, ABILITIES, ABILITIES_SUBTYPE_AND_PT
}
protected Token token;
protected String type;
protected LoseType loseType; // what attributes are lost
private final Token token;
private final Duration duration;
public BecomesCreatureAttachedWithActivatedAbilityOrSpellEffect(Token token, String text, Duration duration) {
this(token, text, duration, LoseType.NONE);
}
public BecomesCreatureAttachedWithActivatedAbilityOrSpellEffect(Token token, String text, Duration duration, LoseType loseType) {
super(duration, Layer.TypeChangingEffects_4, SubLayer.NA, Outcome.BecomeCreature);
this.token = token;
this.loseType = loseType;
super(Outcome.BecomeCreature);
staticText = text;
this.token = token;
this.duration = duration;
}
public BecomesCreatureAttachedWithActivatedAbilityOrSpellEffect(final BecomesCreatureAttachedWithActivatedAbilityOrSpellEffect effect) {
super(effect);
this.token = effect.token.copy();
this.type = effect.type;
this.loseType = effect.loseType;
this.duration = effect.duration;
}
@Override
@ -44,122 +35,19 @@ public class BecomesCreatureAttachedWithActivatedAbilityOrSpellEffect extends Co
return new BecomesCreatureAttachedWithActivatedAbilityOrSpellEffect(this);
}
@Override
public void init(Ability source, Game game) {
super.init(source, game);
Permanent attachedPermanent = game.getPermanent(source.getSourceId());
if (attachedPermanent != null) {
Permanent permanentAttachedTo = game.getPermanent(attachedPermanent.getAttachedTo());
if (permanentAttachedTo != null) {
affectedObjectList.add(new MageObjectReference(permanentAttachedTo, game));
}
}
}
@Override
public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) {
boolean attachedExists = false;
Permanent enchantment = game.getPermanent(source.getSourceId());
if (enchantment != null) {
for (MageObjectReference mageObjectReference : affectedObjectList) {
Permanent permanentAttachedTo = mageObjectReference.getPermanent(game);
if (permanentAttachedTo != null) {
attachedExists = true;
switch (layer) {
case TypeChangingEffects_4:
if (sublayer == SubLayer.NA) {
for (SuperType superType : token.getSuperType()) {
permanentAttachedTo.addSuperType(superType);
}
// card type
switch (loseType) {
case ALL:
case ALL_BUT_COLOR:
permanentAttachedTo.getCardType().clear();
break;
}
for (CardType cardType : token.getCardType()) {
permanentAttachedTo.addCardType(cardType);
}
// sub type
switch (loseType) {
case ALL:
case ALL_BUT_COLOR:
case ABILITIES_SUBTYPE_AND_PT:
permanentAttachedTo.getSubtype(game).retainAll(SubType.getLandTypes());
break;
}
for (SubType subType : token.getSubtype(game)) {
if (!permanentAttachedTo.hasSubtype(subType, game)) {
permanentAttachedTo.getSubtype(game).add(subType);
}
}
}
break;
case ColorChangingEffects_5:
if (sublayer == SubLayer.NA) {
if (loseType == LoseType.ALL) {
permanentAttachedTo.getColor(game).setBlack(false);
permanentAttachedTo.getColor(game).setGreen(false);
permanentAttachedTo.getColor(game).setBlue(false);
permanentAttachedTo.getColor(game).setWhite(false);
permanentAttachedTo.getColor(game).setRed(false);
}
if (token.getColor(game).hasColor()) {
permanentAttachedTo.getColor(game).setColor(token.getColor(game));
}
}
break;
case AbilityAddingRemovingEffects_6:
if (sublayer == SubLayer.NA) {
switch (loseType) {
case ALL:
case ALL_BUT_COLOR:
case ABILITIES:
case ABILITIES_SUBTYPE_AND_PT:
permanentAttachedTo.removeAllAbilities(source.getSourceId(), game);
break;
}
for (Ability ability : token.getAbilities()) {
permanentAttachedTo.addAbility(ability, source.getSourceId(), game);
}
}
break;
case PTChangingEffects_7:
if (sublayer == SubLayer.SetPT_7b) {
permanentAttachedTo.getPower().setValue(token.getPower().getValue());
permanentAttachedTo.getToughness().setValue(token.getToughness().getValue());
}
break;
}
}
if (!attachedExists) {
discard();
}
return true;
}
}
return false;
}
@Override
public boolean apply(Game game, Ability source) {
return false;
Permanent permanent = source.getSourcePermanentOrLKI(game);
if (permanent == null) {
return false;
}
Permanent enchanted = game.getPermanent(permanent.getAttachedTo());
if (enchanted == null) {
return false;
}
game.addEffect(new BecomesCreatureTargetEffect(
token, false, true, duration
).setTargetPointer(new FixedTarget(enchanted, game)), source);
return true;
}
@Override
public boolean hasLayer(Layer layer) {
return layer == Layer.PTChangingEffects_7
|| layer == Layer.AbilityAddingRemovingEffects_6
|| layer == Layer.ColorChangingEffects_5
|| layer == Layer.TypeChangingEffects_4;
}
}

View file

@ -82,78 +82,66 @@ public class BecomesCreatureSourceEffect extends ContinuousEffectImpl implements
} else {
permanent = game.getPermanent(source.getSourceId());
}
if (permanent != null) {
switch (layer) {
case TypeChangingEffects_4:
if (sublayer == SubLayer.NA) {
if (losePreviousTypes) {
permanent.getCardType().clear();
}
for (CardType cardType : token.getCardType()) {
if (permanent.getCardType().contains(cardType)) {
continue;
}
permanent.addCardType(cardType);
}
if (theyAreStillType != null && theyAreStillType.isEmpty() || theyAreStillType == null && permanent.isLand()) {
permanent.getSubtype(game).retainAll(SubType.getLandTypes());
}
if (!token.getSubtype(game).isEmpty()) {
for (SubType subType : token.getSubtype(game)) {
if (permanent.hasSubtype(subType, game)) {
continue;
}
permanent.getSubtype(game).add(subType);
}
}
permanent.setIsAllCreatureTypes(token.isAllCreatureTypes());
}
break;
case ColorChangingEffects_5:
if (sublayer == SubLayer.NA) {
if (token.getColor(game).hasColor()) {
permanent.getColor(game).setColor(token.getColor(game));
}
}
break;
case AbilityAddingRemovingEffects_6:
if (sublayer == SubLayer.NA) {
if (loseAbilities) {
permanent.removeAllAbilities(source.getSourceId(), game);
}
for (Ability ability : token.getAbilities()) {
permanent.addAbility(ability, source.getSourceId(), game);
}
}
break;
case PTChangingEffects_7:
if ((sublayer == SubLayer.CharacteristicDefining_7a && isCharacterDefining())
|| (sublayer == SubLayer.SetPT_7b && !isCharacterDefining())) {
if (power != null) {
permanent.getPower().setValue(power.calculate(game, source, this)); // check all other becomes to use calculate?
} else if (token.getPower() != null) {
permanent.getPower().setValue(token.getPower().getValue());
}
if (toughness != null) {
permanent.getToughness().setValue(toughness.calculate(game, source, this));
} else if (token.getToughness() != null) {
permanent.getToughness().setValue(token.getToughness().getValue());
}
}
break;
if (permanent == null) {
if (duration == Duration.Custom) {
this.discard();
}
return false;
}
switch (layer) {
case TypeChangingEffects_4:
if (losePreviousTypes) {
permanent.getCardType().clear();
}
for (CardType cardType : token.getCardType()) {
permanent.addCardType(cardType);
}
return true;
if (theyAreStillType != null && theyAreStillType.isEmpty()
|| theyAreStillType == null && permanent.isLand()) {
permanent.removeAllCreatureTypes(game);
}
if (!token.getSubtype(game).isEmpty()) {
for (SubType subType : token.getSubtype(game)) {
permanent.addSubType(game, subType);
}
}
permanent.setIsAllCreatureTypes(token.isAllCreatureTypes());
break;
} else if (duration == Duration.Custom) {
this.discard();
case ColorChangingEffects_5:
if (token.getColor(game).hasColor()) {
permanent.getColor(game).setColor(token.getColor(game));
}
break;
case AbilityAddingRemovingEffects_6:
if (loseAbilities) {
permanent.removeAllAbilities(source.getSourceId(), game);
}
for (Ability ability : token.getAbilities()) {
permanent.addAbility(ability, source.getSourceId(), game);
}
break;
case PTChangingEffects_7:
if ((sublayer == SubLayer.CharacteristicDefining_7a && isCharacterDefining())
|| (sublayer == SubLayer.SetPT_7b && !isCharacterDefining())) {
if (power != null) {
permanent.getPower().setValue(power.calculate(game, source, this)); // check all other becomes to use calculate?
} else if (token.getPower() != null) {
permanent.getPower().setValue(token.getPower().getValue());
}
if (toughness != null) {
permanent.getToughness().setValue(toughness.calculate(game, source, this));
} else if (token.getToughness() != null) {
permanent.getToughness().setValue(token.getToughness().getValue());
}
}
break;
}
return false;
return true;
}
@Override
@ -171,7 +159,10 @@ public class BecomesCreatureSourceEffect extends ContinuousEffectImpl implements
@Override
public boolean hasLayer(Layer layer) {
return layer == Layer.PTChangingEffects_7 || layer == Layer.AbilityAddingRemovingEffects_6 || layer == Layer.ColorChangingEffects_5 || layer == Layer.TypeChangingEffects_4;
return layer == Layer.PTChangingEffects_7
|| layer == Layer.AbilityAddingRemovingEffects_6
|| layer == Layer.ColorChangingEffects_5
|| layer == Layer.TypeChangingEffects_4;
}
}

View file

@ -70,82 +70,68 @@ public class BecomesCreatureTargetEffect extends ContinuousEffectImpl {
boolean result = false;
for (UUID permanentId : targetPointer.getTargets(game, source)) {
Permanent permanent = game.getPermanent(permanentId);
if (permanent != null) {
switch (layer) {
case TextChangingEffects_3:
if (sublayer == SubLayer.NA) {
if (loseName) {
permanent.setName(token.getName());
}
}
break;
case TypeChangingEffects_4:
if (sublayer == SubLayer.NA) {
if (loseAllAbilities) {
permanent.getSubtype(game).retainAll(SubType.getLandTypes());
permanent.getCardType().clear(); // remove all CardTypes
permanent.getSubtype(game).addAll(token.getSubtype(game));
} else {
if (removeSubtypes) {
permanent.getSubtype(game).clear();
}
for (SubType t : token.getSubtype(game)) {
if (!permanent.hasSubtype(t, game)) {
permanent.getSubtype(game).add(t);
}
}
}
for (SuperType t : token.getSuperType()) {
if (!permanent.getSuperType().contains(t)) {
permanent.addSuperType(t);
}
}
for (CardType t : token.getCardType()) {
if (!permanent.getCardType().contains(t)) {
permanent.addCardType(t);
}
}
}
break;
case ColorChangingEffects_5:
if (sublayer == SubLayer.NA) {
if (loseAllAbilities) {
permanent.getColor(game).setBlack(false);
permanent.getColor(game).setGreen(false);
permanent.getColor(game).setBlue(false);
permanent.getColor(game).setWhite(false);
permanent.getColor(game).setBlack(false);
}
if (token.getColor(game).hasColor()) {
permanent.getColor(game).setColor(token.getColor(game));
}
}
break;
case AbilityAddingRemovingEffects_6:
if (loseAllAbilities && !keepAbilities) {
permanent.removeAllAbilities(source.getSourceId(), game);
}
if (sublayer == SubLayer.NA) {
if (!token.getAbilities().isEmpty()) {
for (Ability ability : token.getAbilities()) {
permanent.addAbility(ability, source.getSourceId(), game);
}
}
}
break;
case PTChangingEffects_7:
if (sublayer == SubLayer.SetPT_7b) { // CDA can only define a characteristic of either the card or token it comes from.
permanent.getToughness().setValue(token.getToughness().getValue());
permanent.getPower().setValue(token.getPower().getValue());
}
}
result = true;
if (permanent == null) {
continue;
}
switch (layer) {
case TextChangingEffects_3:
if (loseName) {
permanent.setName(token.getName());
}
break;
case TypeChangingEffects_4:
for (CardType t : token.getCardType()) {
permanent.addCardType(t);
}
if (loseAllAbilities || removeSubtypes) {
permanent.removeAllCreatureTypes(game);
}
for (SubType t : token.getSubtype(game)) {
permanent.addSubType(game, t);
}
for (SuperType t : token.getSuperType()) {
if (!permanent.getSuperType().contains(t)) {
permanent.addSuperType(t);
}
}
break;
case ColorChangingEffects_5:
if (loseAllAbilities) {
permanent.getColor(game).setWhite(false);
permanent.getColor(game).setBlue(false);
permanent.getColor(game).setBlack(false);
permanent.getColor(game).setRed(false);
permanent.getColor(game).setGreen(false);
}
if (token.getColor(game).hasColor()) {
permanent.getColor(game).setColor(token.getColor(game));
}
break;
case AbilityAddingRemovingEffects_6:
if (loseAllAbilities && !keepAbilities) {
permanent.removeAllAbilities(source.getSourceId(), game);
}
if (sublayer == SubLayer.NA) {
if (!token.getAbilities().isEmpty()) {
for (Ability ability : token.getAbilities()) {
permanent.addAbility(ability, source.getSourceId(), game);
}
}
}
break;
case PTChangingEffects_7:
if (sublayer == SubLayer.SetPT_7b) { // CDA can only define a characteristic of either the card or token it comes from.
permanent.getToughness().setValue(token.getToughness().getValue());
permanent.getPower().setValue(token.getPower().getValue());
}
}
result = true;
}
if (!result && this.duration == Duration.Custom) {
this.discard();

View file

@ -1,8 +1,3 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package mage.abilities.effects.common.continuous;
import mage.abilities.Ability;
@ -19,15 +14,15 @@ import java.util.UUID;
*/
public class BecomesCreatureTypeTargetEffect extends ContinuousEffectImpl {
protected SubTypeList subtypes = new SubTypeList();
protected boolean loseOther; // loses other creature types
private final SubTypeList subtypes = new SubTypeList();
private final boolean loseOther; // loses other creature types
public BecomesCreatureTypeTargetEffect(Duration duration, SubType subtype) {
this(duration, createArrayList(subtype));
this(duration, new SubTypeList(subtype));
}
public BecomesCreatureTypeTargetEffect(Duration duration, SubType subtype, boolean loseOther) {
this(duration, createArrayList(subtype), loseOther);
this(duration, new SubTypeList(subtype), loseOther);
}
public BecomesCreatureTypeTargetEffect(Duration duration, SubTypeList subtypes) {
@ -35,67 +30,45 @@ public class BecomesCreatureTypeTargetEffect extends ContinuousEffectImpl {
}
public BecomesCreatureTypeTargetEffect(Duration duration, SubTypeList subtypes, boolean loseOther) {
super(duration, Outcome.Detriment);
this.subtypes = subtypes;
super(duration, Layer.TypeChangingEffects_4, SubLayer.NA, Outcome.Detriment);
this.subtypes.addAll(subtypes);
this.staticText = setText();
this.loseOther = loseOther;
}
private static SubTypeList createArrayList(SubType subtype) {
SubTypeList subtypes = new SubTypeList();
subtypes.add(subtype);
return subtypes;
}
public BecomesCreatureTypeTargetEffect(final BecomesCreatureTypeTargetEffect effect) {
super(effect);
this.subtypes.addAll(effect.subtypes);
this.loseOther = effect.loseOther;
}
@Override
public boolean apply(Game game, Ability source) {
return false;
}
@Override
public BecomesCreatureTypeTargetEffect copy() {
return new BecomesCreatureTypeTargetEffect(this);
}
@Override
public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) {
public boolean apply(Game game, Ability source) {
boolean flag = false;
for (UUID targetPermanent : targetPointer.getTargets(game, source)) {
Permanent permanent = game.getPermanent(targetPermanent);
if (permanent != null) {
switch (layer) {
case TypeChangingEffects_4:
if (loseOther) {
permanent.getSubtype(game).retainAll(SubType.getLandTypes());
permanent.getSubtype(game).addAll(subtypes);
} else {
for (SubType subtype : subtypes) {
if (!permanent.hasSubtype(subtype, game)) {
permanent.getSubtype(game).add(subtype);
}
}
}
break;
}
} else {
if (duration == Duration.Custom) {
discard();
}
if (permanent == null) {
continue;
}
flag = true;
if (loseOther) {
permanent.removeAllCreatureTypes(game);
}
for (SubType subtype : subtypes) {
permanent.addSubType(game, subtype);
}
}
if (!flag && duration == Duration.Custom) {
discard();
}
return true;
}
@Override
public boolean hasLayer(Layer layer) {
return layer == Layer.TypeChangingEffects_4;
}
private String setText() {
StringBuilder sb = new StringBuilder();
sb.append("Target creature becomes that type");

View file

@ -1,33 +1,21 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package mage.abilities.effects.common.continuous;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.constants.CardType;
import mage.constants.DependencyType;
import mage.constants.Duration;
import mage.constants.Layer;
import mage.constants.Outcome;
import mage.constants.SubLayer;
import mage.constants.*;
import mage.game.Game;
import mage.game.permanent.Permanent;
/**
*
* @author jeffwadsworth
*/
public class BecomesEnchantmentSourceEffect extends ContinuousEffectImpl implements SourceEffect {
public BecomesEnchantmentSourceEffect() {
super(Duration.Custom, Outcome.AddAbility);
super(Duration.Custom, Layer.TypeChangingEffects_4, SubLayer.NA, Outcome.AddAbility);
staticText = "{this} becomes an Enchantment";
dependencyTypes.add(DependencyType.EnchantmentAddingRemoving);
}
public BecomesEnchantmentSourceEffect(final BecomesEnchantmentSourceEffect effect) {
@ -45,35 +33,17 @@ public class BecomesEnchantmentSourceEffect extends ContinuousEffectImpl impleme
affectedObjectList.add(new MageObjectReference(source.getSourceId(), game));
}
@Override
public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) {
Permanent permanent = affectedObjectList.get(0).getPermanent(game);
if (permanent != null) {
switch (layer) {
case TypeChangingEffects_4:
if (sublayer == SubLayer.NA) {
permanent.getCardType().clear();
permanent.getSubtype(game).clear();
if (!permanent.getCardType().contains(CardType.ENCHANTMENT)) {
permanent.getCardType().add(CardType.ENCHANTMENT);
}
}
break;
}
return true;
}
this.discard();
return false;
}
@Override
public boolean apply(Game game, Ability source) {
return false;
Permanent permanent = affectedObjectList.get(0).getPermanent(game);
if (permanent == null) {
this.discard();
return false;
}
permanent.getCardType().clear();
permanent.getCardType().add(CardType.ENCHANTMENT);
permanent.getSubtype(game).retainAll(SubType.getEnchantmentTypes());
permanent.setIsAllCreatureTypes(false);
return true;
}
@Override
public boolean hasLayer(Layer layer) {
return Layer.TypeChangingEffects_4 == layer;
}
}

View file

@ -75,7 +75,7 @@ public class BecomesFaceDownCreatureAllEffect extends ContinuousEffectImpl imple
permanent.getSuperType().clear();
permanent.getCardType().clear();
permanent.addCardType(CardType.CREATURE);
permanent.getSubtype(game).clear();
permanent.removeAllSubTypes(game);
permanent.getManaCost().clear();
break;
case ColorChangingEffects_5:

View file

@ -136,7 +136,7 @@ public class BecomesFaceDownCreatureEffect extends ContinuousEffectImpl implemen
permanent.getSuperType().clear();
permanent.getCardType().clear();
permanent.addCardType(CardType.CREATURE);
permanent.getSubtype(game).clear();
permanent.removeAllSubTypes(game);
break;
case ColorChangingEffects_5:
permanent.getColor(game).setColor(new ObjectColor());

View file

@ -8,6 +8,7 @@ package mage.abilities.effects.common.continuous;
import mage.abilities.Ability;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.constants.*;
import mage.filter.StaticFilters;
import mage.filter.common.FilterCreaturePermanent;
import mage.game.Game;
import mage.game.permanent.Permanent;
@ -18,33 +19,26 @@ import mage.util.SubTypeList;
*/
public class BecomesSubtypeAllEffect extends ContinuousEffectImpl {
protected SubTypeList subtypes = new SubTypeList();
protected boolean loseOther; // loses other subtypes
protected FilterCreaturePermanent filter;
private final SubTypeList subtypes = new SubTypeList();
private final boolean loseOther; // loses other subtypes
private final FilterCreaturePermanent filter;
public BecomesSubtypeAllEffect(Duration duration, SubType subtype) {
this(duration, createArrayList(subtype));
this(duration, new SubTypeList(subtype));
}
public BecomesSubtypeAllEffect(Duration duration, SubTypeList subtypes) {
this(duration, subtypes, new FilterCreaturePermanent("All creatures"), true);
this(duration, subtypes, StaticFilters.FILTER_PERMANENT_CREATURE, true);
}
public BecomesSubtypeAllEffect(Duration duration,
SubTypeList subtypes, FilterCreaturePermanent filter, boolean loseOther) {
super(duration, Outcome.Detriment);
this.subtypes = subtypes;
public BecomesSubtypeAllEffect(Duration duration, SubTypeList subtypes, FilterCreaturePermanent filter, boolean loseOther) {
super(duration, Layer.TypeChangingEffects_4, SubLayer.NA, Outcome.Detriment);
this.subtypes.addAll(subtypes);
this.staticText = setText();
this.loseOther = loseOther;
this.filter = filter;
}
private static SubTypeList createArrayList(SubType subtype) {
SubTypeList subtypes = new SubTypeList();
subtypes.add(subtype);
return subtypes;
}
public BecomesSubtypeAllEffect(final BecomesSubtypeAllEffect effect) {
super(effect);
this.subtypes.addAll(effect.subtypes);
@ -52,47 +46,32 @@ public class BecomesSubtypeAllEffect extends ContinuousEffectImpl {
this.filter = effect.filter;
}
@Override
public boolean apply(Game game, Ability source) {
return false;
}
@Override
public BecomesSubtypeAllEffect copy() {
return new BecomesSubtypeAllEffect(this);
}
@Override
public boolean apply(Layer layer, SubLayer sublayer, Ability source,
Game game) {
public boolean apply(Game game, Ability source) {
boolean flag = false;
for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, source.getControllerId(), source.getSourceId(), game)) {
if (permanent != null) {
switch (layer) {
case TypeChangingEffects_4:
if (loseOther) {
permanent.getSubtype(game).retainAll(SubType.getLandTypes());
permanent.getSubtype(game).addAll(subtypes);
} else {
for (SubType subtype : subtypes) {
if (!permanent.hasSubtype(subtype, game)) {
permanent.getSubtype(game).add(subtype);
}
}
}
break;
}
} else if (duration == Duration.Custom) {
discard();
if (permanent == null) {
continue;
}
flag = true;
if (loseOther) {
permanent.removeAllCreatureTypes(game);
}
for (SubType subtype : subtypes) {
permanent.addSubType(game, subtype);
}
}
if (!flag && duration == Duration.Custom) {
discard();
}
return true;
}
@Override
public boolean hasLayer(Layer layer) {
return layer == Layer.TypeChangingEffects_4;
}
private String setText() {
StringBuilder sb = new StringBuilder();
sb.append("Target creature becomes that type");

View file

@ -9,6 +9,7 @@ import mage.abilities.costs.AlternativeCostSourceAbility;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.cards.AdventureCardSpell;
import mage.cards.Card;
import mage.cards.ModalDoubleFacesCardHalf;
import mage.cards.SplitCardHalf;
import mage.constants.*;
import mage.filter.FilterCard;
@ -22,7 +23,7 @@ import java.util.UUID;
public class CastFromHandWithoutPayingManaCostEffect extends ContinuousEffectImpl {
private final AlternativeCostSourceAbility alternativeCastingCostAbility;
public CastFromHandWithoutPayingManaCostEffect() {
this(StaticFilters.FILTER_CARDS_NON_LAND, true);
}
@ -38,7 +39,7 @@ public class CastFromHandWithoutPayingManaCostEffect extends ContinuousEffectImp
condition = new CompoundCondition(SourceIsSpellCondition.instance, IsBeingCastFromHandCondition.instance);
} else {
condition = SourceIsSpellCondition.instance;
}
}
this.alternativeCastingCostAbility = new AlternativeCostSourceAbility(null, condition, null, filter, true);
this.staticText = "You may cast " + filter.getMessage()
+ (fromHand ? " from your hand" : "")
@ -88,9 +89,9 @@ enum IsBeingCastFromHandCondition implements Condition {
@Override
public boolean apply(Game game, Ability source) {
MageObject object = game.getObject(source.getSourceId());
if (object instanceof SplitCardHalf || object instanceof AdventureCardSpell) {
UUID splitCardId = ((Card) object).getMainCard().getId();
object = game.getObject(splitCardId);
if (object instanceof SplitCardHalf || object instanceof AdventureCardSpell || object instanceof ModalDoubleFacesCardHalf) {
UUID mainCardId = ((Card) object).getMainCard().getId();
object = game.getObject(mainCardId);
}
if (object instanceof Spell) { // needed to check if it can be cast by alternate cost
Spell spell = (Spell) object;

View file

@ -40,10 +40,8 @@ public class CreaturesBecomeOtherTypeEffect extends ContinuousEffectImpl {
@Override
public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) {
if (layer == Layer.TypeChangingEffects_4) {
for (Permanent object: game.getBattlefield().getActivePermanents(this.filter, source.getControllerId(), game)) {
if (!object.hasSubtype(this.subType, game)) {
object.getSubtype(game).add(this.subType);
}
for (Permanent object : game.getBattlefield().getActivePermanents(this.filter, source.getControllerId(), game)) {
object.addSubType(game, this.subType);
}
}

View file

@ -0,0 +1,44 @@
package mage.abilities.effects.common.continuous;
import mage.abilities.Ability;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.constants.Duration;
import mage.constants.Layer;
import mage.constants.Outcome;
import mage.constants.SubLayer;
import mage.game.Game;
import mage.game.permanent.Permanent;
/**
* @author TheElk801
*/
public class GainAllCreatureTypesAttachedEffect extends ContinuousEffectImpl {
public GainAllCreatureTypesAttachedEffect() {
super(Duration.WhileOnBattlefield, Layer.TypeChangingEffects_4, SubLayer.NA, Outcome.Neutral);
staticText = "and is every creature type";
}
public GainAllCreatureTypesAttachedEffect(final GainAllCreatureTypesAttachedEffect effect) {
super(effect);
}
@Override
public GainAllCreatureTypesAttachedEffect copy() {
return new GainAllCreatureTypesAttachedEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Permanent equipment = game.getPermanent(source.getSourceId());
if (equipment == null) {
return false;
}
Permanent permanent = game.getPermanent(equipment.getAttachedTo());
if (permanent == null) {
return false;
}
permanent.setIsAllCreatureTypes(true);
return true;
}
}

View file

@ -0,0 +1,53 @@
package mage.abilities.effects.common.continuous;
import mage.abilities.Ability;
import mage.abilities.Mode;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.constants.Duration;
import mage.constants.Layer;
import mage.constants.Outcome;
import mage.constants.SubLayer;
import mage.game.Game;
import mage.game.permanent.Permanent;
import java.util.UUID;
/**
* @author TheElk801
*/
public class GainAllCreatureTypesTargetEffect extends ContinuousEffectImpl {
public GainAllCreatureTypesTargetEffect(Duration duration) {
super(duration, Layer.TypeChangingEffects_4, SubLayer.NA, Outcome.Neutral);
}
public GainAllCreatureTypesTargetEffect(final GainAllCreatureTypesTargetEffect effect) {
super(effect);
}
@Override
public GainAllCreatureTypesTargetEffect copy() {
return new GainAllCreatureTypesTargetEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
int affectedTargets = 0;
for (UUID permanentId : targetPointer.getTargets(game, source)) {
Permanent target = game.getPermanent(permanentId);
if (target != null) {
target.setIsAllCreatureTypes(true);
affectedTargets++;
}
}
return affectedTargets > 0;
}
@Override
public String getText(Mode mode) {
if (staticText != null && !staticText.isEmpty()) {
return staticText;
}
return "target " + mode.getTargets().get(0).getTargetName() + " gains all creature types " + duration.toString();
}
}

View file

@ -0,0 +1,71 @@
package mage.abilities.effects.common.continuous;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.constants.*;
import mage.game.Game;
import mage.util.SubTypeList;
/**
* @author TheElk801
*/
public class HasSubtypesSourceEffect extends ContinuousEffectImpl {
private final SubTypeList subtypes = new SubTypeList();
public HasSubtypesSourceEffect(SubType... subTypes) {
super(Duration.EndOfGame, Layer.TypeChangingEffects_4, SubLayer.NA, Outcome.Benefit);
subtypes.add(subTypes);
this.staticText = setText();
}
public HasSubtypesSourceEffect(final HasSubtypesSourceEffect effect) {
super(effect);
this.subtypes.addAll(effect.subtypes);
}
@Override
public HasSubtypesSourceEffect copy() {
return new HasSubtypesSourceEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
MageObject sourceObject = game.getObject(source.getSourceId());
if (sourceObject == null) {
return false;
}
for (SubType subType : subtypes) {
sourceObject.addSubType(game, subType);
}
return true;
}
private String setText() {
String s = "{this} is also ";
switch (subtypes.size()) {
case 0:
throw new UnsupportedOperationException("Can't have zero subtypes");
case 1:
s += subtypes.get(0).getIndefiniteArticle() + " " + subtypes.get(0);
break;
case 2:
s += subtypes.get(0).getIndefiniteArticle() + " " + subtypes.get(0);
s += " and ";
s += subtypes.get(1).getIndefiniteArticle() + " " + subtypes.get(1);
break;
default:
for (int i = 0; i < subtypes.size(); i++) {
if (i == 0) {
s += subtypes.get(0).getIndefiniteArticle() + " " + subtypes.get(0) + ", ";
} else if (i == subtypes.size() - 1) {
s += "and " + subtypes.get(0);
} else {
s += subtypes.get(0) + ", ";
}
}
}
return s;
}
}

View file

@ -3,7 +3,10 @@ package mage.abilities.effects.common.continuous;
import mage.abilities.Ability;
import mage.abilities.Mode;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.constants.*;
import mage.constants.Duration;
import mage.constants.Layer;
import mage.constants.Outcome;
import mage.constants.SubLayer;
import mage.game.Game;
import mage.game.permanent.Permanent;
@ -29,8 +32,8 @@ public class LoseAllCreatureTypesTargetEffect extends ContinuousEffectImpl {
public boolean apply(Game game, Ability source) {
Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source));
if (permanent != null) {
permanent.setIsAllCreatureTypes(false);
return permanent.getSubtype(game).retainAll(SubType.getLandTypes());
permanent.removeAllCreatureTypes(game);
return true;
}
return false;
}

View file

@ -24,7 +24,7 @@ public class LoseCreatureTypeSourceEffect extends ContinuousEffectImpl implement
* @param lessThan
*/
public LoseCreatureTypeSourceEffect(DynamicValue dynamicValue, int lessThan) {
super(Duration.WhileOnBattlefield, Outcome.Detriment);
super(Duration.WhileOnBattlefield, Layer.TypeChangingEffects_4, SubLayer.NA, Outcome.Detriment);
this.dynamicValue = dynamicValue;
this.lessThan = lessThan;
setText();
@ -53,31 +53,22 @@ public class LoseCreatureTypeSourceEffect extends ContinuousEffectImpl implement
}
@Override
public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) {
public boolean apply(Game game, Ability source) {
if (dynamicValue.calculate(game, source, this) >= lessThan) {
return false;
}
Permanent permanent = game.getPermanent(source.getSourceId());
if (permanent != null) {
switch (layer) {
case TypeChangingEffects_4:
if (sublayer == SubLayer.NA) {
permanent.getCardType().remove(CardType.CREATURE);
permanent.getSubtype(game).retainAll(SubType.getLandTypes());
if (permanent.isAttacking() || permanent.getBlocking() > 0) {
permanent.removeFromCombat(game);
}
}
break;
}
return true;
if (permanent == null) {
return false;
}
return false;
}
@Override
public boolean apply(Game game, Ability source) {
return false;
permanent.getCardType().remove(CardType.CREATURE);
if (!permanent.isTribal()) {
permanent.removeAllCreatureTypes(game);
}
if (permanent.isAttacking() || permanent.getBlocking() > 0) {
permanent.removeFromCombat(game);
}
return true;
}
private void setText() {
@ -86,10 +77,4 @@ public class LoseCreatureTypeSourceEffect extends ContinuousEffectImpl implement
sb.append(CardUtil.numberToText(lessThan)).append(", {this} isn't a creature");
staticText = sb.toString();
}
@Override
public boolean hasLayer(Layer layer) {
return layer == Layer.TypeChangingEffects_4;
}
}

View file

@ -22,20 +22,6 @@ public class SetCardSubtypeAttachedEffect extends ContinuousEffectImpl {
this.setText();
}
/*public SetCardSubtypeAttachedEffect(SubType setSubtype, Duration duration, AttachmentType attachmentType) {
super(duration, Layer.TypeChangingEffects_4, SubLayer.NA, Outcome.Benefit);
this.setSubtypes.add(setSubtype);
this.attachmentType = attachmentType;
setText();
}
public SetCardSubtypeAttachedEffect(List<String> setSubtypes, Duration duration, AttachmentType attachmentType) {
super(duration, Layer.TypeChangingEffects_4, SubLayer.NA, Outcome.Benefit);
this.setSubtypes.addAll(setSubtypes);
this.attachmentType = attachmentType;
setText();
}*/
public SetCardSubtypeAttachedEffect(final SetCardSubtypeAttachedEffect effect) {
super(effect);
this.setSubtypes = effect.setSubtypes;
@ -45,13 +31,15 @@ public class SetCardSubtypeAttachedEffect extends ContinuousEffectImpl {
@Override
public boolean apply(Game game, Ability source) {
Permanent equipment = game.getPermanent(source.getSourceId());
if (equipment != null && equipment.getAttachedTo() != null) {
Permanent target = game.getPermanent(equipment.getAttachedTo());
if (target != null) {
target.getSubtype(game).retainAll(SubType.getLandTypes());
target.getSubtype(game).addAll(setSubtypes);
}
if (equipment == null || equipment.getAttachedTo() == null) {
return true;
}
Permanent target = game.getPermanent(equipment.getAttachedTo());
if (target == null) {
return true;
}
target.removeAllCreatureTypes(game);
target.getSubtype(game).addAll(setSubtypes);
return true;
}

View file

@ -9,9 +9,9 @@ import mage.game.Game;
import mage.game.permanent.Permanent;
/**
*
* IMPORTANT: This only adds the chosen subtype while the source permanent is entering the battlefield.
* You should also use @link{mage.abilities.effects.common.continuous.AddChosenSubtypeEffect} to make the subtype persist.
*
* @author LevelX2
*/
public class EnterAttributeAddChosenSubtypeEffect extends OneShotEffect {
@ -35,9 +35,7 @@ public class EnterAttributeAddChosenSubtypeEffect extends OneShotEffect {
Permanent permanent = game.getPermanentEntering(source.getSourceId());
SubType subtype = (SubType) game.getState().getValue(source.getSourceId() + "_type");
if (permanent != null && subtype != null) {
if (!permanent.getSubtype(game).contains(subtype)) {
permanent.getSubtype(game).add(subtype);
}
permanent.addSubType(game, subtype);
return true;
}
return false;

View file

@ -76,7 +76,7 @@ public class FatesealEffect extends OneShotEffect {
}
// move cards to the top of the library
controller.putCardsOnTopOfLibrary(cards, game, source, true);
game.fireEvent(new GameEvent(GameEvent.EventType.FATESEAL, opponent.getId(), source.getSourceId(), source.getControllerId()));
game.fireEvent(new GameEvent(GameEvent.EventType.FATESEALED, opponent.getId(), source.getSourceId(), source.getControllerId()));
controller.setTopCardRevealed(revealed);
return true;
}

View file

@ -1,24 +1,31 @@
package mage.abilities.keyword;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.*;
import mage.abilities.effects.AsThoughEffectImpl;
import mage.abilities.effects.ContinuousRuleModifyingEffectImpl;
import mage.abilities.effects.ReplacementEffectImpl;
import mage.cards.Card;
import mage.cards.ModalDoubleFacesCardHalf;
import mage.cards.SplitCardHalf;
import mage.constants.*;
import mage.constants.AsThoughEffectType;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.ZoneChangeEvent;
import mage.game.stack.Spell;
import mage.players.Player;
import java.util.UUID;
/**
* Aftermath
*
* <p>
* TODO: Implement once we get details on the comprehensive rules meaning of the
* ability
*
* <p>
* Current text is a shell copied from Flashback
*
* @author stravant
@ -113,9 +120,7 @@ class AftermathCantCastFromHand extends ContinuousRuleModifyingEffectImpl {
Card card = game.getCard(event.getSourceId());
if (card != null && card.getId().equals(source.getSourceId())) {
Zone zone = game.getState().getZone(card.getId());
if (zone != null && (zone != Zone.GRAVEYARD)) {
return true;
}
return zone != null && (zone != Zone.GRAVEYARD);
}
return false;
}
@ -155,14 +160,16 @@ class AftermathExileAsResolvesFromGraveyard extends ReplacementEffectImpl {
sourceCard = ((SplitCardHalf) sourceCard).getParentCard();
sourceId = sourceCard.getId();
}
if (sourceCard instanceof ModalDoubleFacesCardHalf) {
sourceCard = ((ModalDoubleFacesCardHalf) sourceCard).getParentCard();
sourceId = sourceCard.getId();
}
if (event.getTargetId().equals(sourceId)) {
// Moving this spell from stack to yard
Spell spell = game.getStack().getSpell(source.getSourceId());
if (spell != null && spell.getFromZone() == Zone.GRAVEYARD) {
// And this spell was cast from the graveyard, so we need to exile it
return true;
}
// And this spell was cast from the graveyard, so we need to exile it
return spell != null && spell.getFromZone() == Zone.GRAVEYARD;
}
}
return false;
@ -174,6 +181,9 @@ class AftermathExileAsResolvesFromGraveyard extends ReplacementEffectImpl {
if (sourceCard instanceof SplitCardHalf) {
sourceCard = ((SplitCardHalf) sourceCard).getParentCard();
}
if (sourceCard instanceof ModalDoubleFacesCardHalf) {
sourceCard = ((ModalDoubleFacesCardHalf) sourceCard).getParentCard();
}
if (sourceCard != null) {
Player player = game.getPlayer(sourceCard.getOwnerId());
if (player != null) {

View file

@ -5,14 +5,18 @@ import mage.abilities.Ability;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.OneShotEffect;
import mage.cards.Card;
import mage.cards.Cards;
import mage.cards.CardsImpl;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.game.ExileZone;
import mage.filter.FilterCard;
import mage.filter.StaticFilters;
import mage.filter.common.FilterLandCard;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.stack.Spell;
import mage.players.Player;
import mage.target.common.TargetCardInExile;
/**
* @author BetaSteward_at_googlemail.com
@ -25,7 +29,8 @@ public class CascadeAbility extends TriggeredAbilityImpl {
+ "nonland card that costs less."
+ " You may cast it without paying its mana cost. "
+ "Put the exiled cards on the bottom in a random order.)</i>";
private boolean withReminder;
private final boolean withReminder;
private static final FilterCard filter = new FilterLandCard("land card (to put onto the battlefield)");
public CascadeAbility() {
this(true);
@ -36,7 +41,7 @@ public class CascadeAbility extends TriggeredAbilityImpl {
this.withReminder = withReminder;
}
public CascadeAbility(final CascadeAbility ability) {
private CascadeAbility(final CascadeAbility ability) {
super(ability);
this.withReminder = ability.withReminder;
}
@ -71,11 +76,11 @@ public class CascadeAbility extends TriggeredAbilityImpl {
class CascadeEffect extends OneShotEffect {
public CascadeEffect() {
CascadeEffect() {
super(Outcome.PutCardInPlay);
}
public CascadeEffect(CascadeEffect effect) {
private CascadeEffect(CascadeEffect effect) {
super(effect);
}
@ -86,8 +91,7 @@ class CascadeEffect extends OneShotEffect {
if (controller == null) {
return false;
}
ExileZone exile = game.getExile().createZone(source.getSourceId(),
controller.getName() + " Cascade");
Cards cards = new CardsImpl();
card = game.getCard(source.getSourceId());
if (card == null) {
return false;
@ -98,33 +102,43 @@ class CascadeEffect extends OneShotEffect {
if (card == null) {
break;
}
controller.moveCardsToExile(card, source, game, true, exile.getId(), exile.getName());
cards.add(card);
controller.moveCards(card, Zone.EXILED, source, game);
} while (controller.canRespond()
&& (card.isLand()
|| !cardThatCostsLess(sourceCost, card, game)));
&& (card.isLand() || card.getConvertedManaCost() >= sourceCost));
controller.getLibrary().reset(); // set back empty draw state if that caused an empty draw
if (card != null) {
if (controller.chooseUse(outcome, "Use cascade effect on " + card.getLogName() + '?', source, game)) {
game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), Boolean.TRUE);
controller.cast(controller.chooseAbilityForCast(card, game, true),
game, true, new ApprovingObject(source, game));
game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), null);
}
GameEvent event = GameEvent.getEvent(
GameEvent.EventType.CASCADE_LAND, source.getSourceId(),
source.getSourceId(), source.getControllerId(), 0
);
game.replaceEvent(event);
if (event.getAmount() > 0) {
TargetCardInExile target = new TargetCardInExile(
0, event.getAmount(), StaticFilters.FILTER_CARD_LAND, null, true
);
controller.choose(Outcome.PutCardInPlay, cards, target, game);
controller.moveCards(
new CardsImpl(target.getTargets()).getCards(game), Zone.BATTLEFIELD,
source, game, true, false, false, null
);
}
if (card != null && controller.chooseUse(
outcome, "Use cascade effect on " + card.getLogName() + '?', source, game
)) {
game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), Boolean.TRUE);
controller.cast(controller.chooseAbilityForCast(card, game, true),
game, true, new ApprovingObject(source, game));
game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), null);
}
// Move the remaining cards to the buttom of the library in a random order
return controller.putCardsOnBottomOfLibrary(new CardsImpl(exile), game, source, false);
cards.removeIf(uuid -> game.getState().getZone(uuid) != Zone.EXILED);
return controller.putCardsOnBottomOfLibrary(cards, game, source, false);
}
@Override
public CascadeEffect copy() {
return new CascadeEffect(this);
}
private boolean cardThatCostsLess(int value, Card card, Game game) {
return card.getConvertedManaCost() < value;
}
}

View file

@ -1,4 +1,3 @@
package mage.abilities.keyword;
import mage.ObjectColor;
@ -19,7 +18,6 @@ import mage.players.Player;
import mage.util.CardUtil;
/**
*
* @author LevelX2
*/
public class EmbalmAbility extends ActivatedAbilityImpl {
@ -89,9 +87,7 @@ class EmbalmEffect extends OneShotEffect {
EmptyToken token = new EmptyToken();
CardUtil.copyTo(token).from(card); // needed so that entersBattlefied triggered abilities see the attributes (e.g. Master Biomancer)
token.getColor(game).setColor(ObjectColor.WHITE);
if (!token.hasSubtype(SubType.ZOMBIE, game)) {
token.getSubtype(game).add(0, SubType.ZOMBIE);
}
token.addSubType(game, SubType.ZOMBIE);
token.getManaCost().clear();
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.EMBALMED_CREATURE, token.getId(), source.getSourceId(), controller.getId()));
token.putOntoBattlefield(1, game, source.getSourceId(), controller.getId(), false, false, null);

View file

@ -1,4 +1,3 @@
package mage.abilities.keyword;
import mage.ObjectColor;
@ -19,7 +18,6 @@ import mage.players.Player;
import mage.util.CardUtil;
/**
*
* @author igoudt
*/
public class EternalizeAbility extends ActivatedAbilityImpl {
@ -94,9 +92,7 @@ class EternalizeEffect extends OneShotEffect {
EmptyToken token = new EmptyToken();
CardUtil.copyTo(token).from(card); // needed so that entersBattlefied triggered abilities see the attributes (e.g. Master Biomancer)
token.getColor(game).setColor(ObjectColor.BLACK);
if (!token.hasSubtype(SubType.ZOMBIE, game)) {
token.getSubtype(game).add(0, SubType.ZOMBIE);
}
token.addSubType(game, SubType.ZOMBIE);
token.getManaCost().clear();
token.removePTCDA();
token.getPower().modifyBaseValue(4);

View file

@ -7,6 +7,7 @@ import mage.abilities.costs.Costs;
import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.ReplacementEffectImpl;
import mage.cards.Card;
import mage.cards.ModalDoubleFacesCard;
import mage.cards.SplitCard;
import mage.constants.*;
import mage.game.Game;
@ -68,12 +69,18 @@ public class FlashbackAbility extends SpellAbility {
}
// Flashback can never cast a split card by Fuse, because Fuse only works from hand
// https://tappedout.net/mtg-questions/snapcaster-mage-and-flashback-on-a-fuse-card-one-or-both-halves-legal-targets/
if (card.isSplitCard()) {
if (card instanceof SplitCard) {
if (((SplitCard) card).getLeftHalfCard().getName().equals(abilityName)) {
return ((SplitCard) card).getLeftHalfCard().getSpellAbility().canActivate(playerId, game);
} else if (((SplitCard) card).getRightHalfCard().getName().equals(abilityName)) {
return ((SplitCard) card).getRightHalfCard().getSpellAbility().canActivate(playerId, game);
}
} else if (card instanceof ModalDoubleFacesCard) {
if (((ModalDoubleFacesCard) card).getLeftHalfCard().getName().equals(abilityName)) {
return ((ModalDoubleFacesCard) card).getLeftHalfCard().getSpellAbility().canActivate(playerId, game);
} else if (((ModalDoubleFacesCard) card).getRightHalfCard().getName().equals(abilityName)) {
return ((ModalDoubleFacesCard) card).getRightHalfCard().getSpellAbility().canActivate(playerId, game);
}
}
return card.getSpellAbility().canActivate(playerId, game);
}
@ -87,12 +94,18 @@ public class FlashbackAbility extends SpellAbility {
if (card != null) {
if (spellAbilityToResolve == null) {
SpellAbility spellAbilityCopy = null;
if (card.isSplitCard()) {
if (card instanceof SplitCard) {
if (((SplitCard) card).getLeftHalfCard().getName().equals(abilityName)) {
spellAbilityCopy = ((SplitCard) card).getLeftHalfCard().getSpellAbility().copy();
} else if (((SplitCard) card).getRightHalfCard().getName().equals(abilityName)) {
spellAbilityCopy = ((SplitCard) card).getRightHalfCard().getSpellAbility().copy();
}
} else if (card instanceof ModalDoubleFacesCard) {
if (((ModalDoubleFacesCard) card).getLeftHalfCard().getName().equals(abilityName)) {
spellAbilityCopy = ((ModalDoubleFacesCard) card).getLeftHalfCard().getSpellAbility().copy();
} else if (((ModalDoubleFacesCard) card).getRightHalfCard().getName().equals(abilityName)) {
spellAbilityCopy = ((ModalDoubleFacesCard) card).getRightHalfCard().getSpellAbility().copy();
}
} else {
spellAbilityCopy = card.getSpellAbility().copy();
}

View file

@ -52,9 +52,10 @@ public class TransformAbility extends SimpleStaticAbility {
for (CardType type : sourceCard.getCardType()) {
permanent.addCardType(type);
}
permanent.getSubtype(game).clear();
permanent.removeAllSubTypes(game);
permanent.setIsAllCreatureTypes(sourceCard.isAllCreatureTypes());
for (SubType type : sourceCard.getSubtype(game)) {
permanent.getSubtype(game).add(type);
permanent.addSubType(game, type);
}
permanent.getSuperType().clear();
for (SuperType type : sourceCard.getSuperType()) {

View file

@ -1,8 +1,3 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package mage.cards;
import mage.abilities.Modes;

View file

@ -1,7 +1,5 @@
package mage.cards;
import java.util.List;
import java.util.UUID;
import mage.MageObject;
import mage.Mana;
import mage.abilities.Abilities;
@ -16,6 +14,9 @@ import mage.game.Game;
import mage.game.GameState;
import mage.game.permanent.Permanent;
import java.util.List;
import java.util.UUID;
public interface Card extends MageObject {
UUID getOwnerId();
@ -29,8 +30,9 @@ public interface Card extends MageObject {
/**
* For cards: return all basic and dynamic abilities
* For permanents: return all basic and dynamic abilities
*
* @param game
* @return
* @return
*/
Abilities<Ability> getAbilities(Game game);
@ -62,8 +64,6 @@ public interface Card extends MageObject {
String getFlipCardName();
boolean isSplitCard();
boolean isTransformable();
void setTransformable(boolean transformable);
@ -76,21 +76,23 @@ public interface Card extends MageObject {
void addInfo(String key, String value, Game game);
// WARNING, don't add new move/remove methods (if you add then you must override it in all multi-part cards like Split or MDF)
/**
* Moves the card to the specified zone
*
* @param zone
* @param sourceId
* @param game
* @param flag If zone
* <ul>
* <li>LIBRARY: <ul><li>true - put on top</li><li>false - put on
* bottom</li></ul></li>
* <li>BATTLEFIELD: <ul><li>true - tapped</li><li>false -
* untapped</li></ul></li>
* <li>GRAVEYARD: <ul><li>true - not from Battlefield</li><li>false - from
* Battlefield</li></ul></li>
* </ul>
* @param flag If zone
* <ul>
* <li>LIBRARY: <ul><li>true - put on top</li><li>false - put on
* bottom</li></ul></li>
* <li>BATTLEFIELD: <ul><li>true - tapped</li><li>false -
* untapped</li></ul></li>
* <li>GRAVEYARD: <ul><li>true - not from Battlefield</li><li>false - from
* Battlefield</li></ul></li>
* </ul>
* @return true if card was moved to zone
*/
boolean moveToZone(Zone zone, UUID sourceId, Game game, boolean flag);
@ -100,8 +102,8 @@ public interface Card extends MageObject {
/**
* Moves the card to an exile zone
*
* @param exileId set to null for generic exile zone
* @param name used for exile zone with the specified exileId
* @param exileId set to null for generic exile zone
* @param name used for exile zone with the specified exileId
* @param sourceId
* @param game
* @return true if card was moved to zone
@ -112,6 +114,7 @@ public interface Card extends MageObject {
boolean cast(Game game, Zone fromZone, SpellAbility ability, UUID controllerId);
// WARNING, don't add new move/remove methods (if you add then you must override it in all multi-parts card like Split Half or MDF Half)
boolean removeFromZone(Game game, Zone fromZone, UUID sourceId);
boolean putOntoBattlefield(Game game, Zone fromZone, UUID sourceId, UUID controllerId);

View file

@ -1,13 +1,10 @@
package mage.cards;
import com.google.common.collect.ImmutableList;
import mage.MageObject;
import mage.MageObjectImpl;
import mage.Mana;
import mage.ObjectColor;
import mage.abilities.*;
import mage.abilities.hint.Hint;
import mage.abilities.hint.HintUtils;
import mage.abilities.keyword.FlashbackAbility;
import mage.abilities.mana.ActivatedManaAbilityImpl;
import mage.cards.repository.PluginClassloaderRegistery;
@ -22,6 +19,7 @@ import mage.game.events.ZoneChangeEvent;
import mage.game.permanent.Permanent;
import mage.game.stack.Spell;
import mage.game.stack.StackObject;
import mage.util.CardUtil;
import mage.util.GameLog;
import mage.util.SubTypeList;
import mage.watchers.Watcher;
@ -57,10 +55,7 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
protected boolean flipCard;
protected String flipCardName;
protected boolean usesVariousArt = false;
protected boolean splitCard;
protected boolean morphCard;
protected boolean modalDFC; // modal double faces card
protected List<UUID> attachments = new ArrayList<>();
public CardImpl(UUID ownerId, CardSetInfo setInfo, CardType[] cardTypes, String costs) {
@ -139,9 +134,7 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
flipCard = card.flipCard;
flipCardName = card.flipCardName;
usesVariousArt = card.usesVariousArt;
splitCard = card.splitCard;
morphCard = card.morphCard;
modalDFC = card.modalDFC;
this.attachments.addAll(card.attachments);
}
@ -223,52 +216,16 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
game.getState().getCardState(objectId).addInfo(key, value);
}
protected static final List<String> rulesError = ImmutableList.of("Exception occurred in rules generation");
@Override
public List<String> getRules() {
try {
return getAbilities().getRules(this.getName());
} catch (Exception e) {
logger.info("Exception in rules generation for card: " + this.getName(), e);
}
return rulesError;
Abilities<Ability> sourceAbilities = this.getAbilities();
return CardUtil.getCardRulesWithAdditionalInfo(this.getId(), this.getName(), sourceAbilities, sourceAbilities);
}
@Override
public List<String> getRules(Game game) {
try {
List<String> rules = getAbilities(game).getRules(getName());
if (game != null) {
// debug state
for (String data : game.getState().getCardState(objectId).getInfo().values()) {
rules.add(data);
}
// ability hints
List<String> abilityHints = new ArrayList<>();
if (HintUtils.ABILITY_HINTS_ENABLE) {
for (Ability ability : abilities) {
for (Hint hint : ability.getHints()) {
String s = hint.getText(game, ability);
if (s != null && !s.isEmpty()) {
abilityHints.add(s);
}
}
}
}
// restrict hints only for permanents, not cards
// total hints
if (!abilityHints.isEmpty()) {
rules.add(HintUtils.HINT_START_MARK);
HintUtils.appendHints(rules, abilityHints);
}
}
return rules;
} catch (Exception e) {
logger.error("Exception in rules generation for card: " + this.getName(), e);
}
return rulesError;
Abilities<Ability> sourceAbilities = this.getAbilities(game);
return CardUtil.getCardRulesWithAdditionalInfo(game, this.getId(), this.getName(), sourceAbilities, sourceAbilities);
}
/**
@ -314,7 +271,8 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
// workaround to add dynamic flashback ability from main card to all parts (example: Snapcaster Mage gives flashback to split card)
if (!this.getId().equals(this.getMainCard().getId())) {
CardState mainCardState = game.getState().getCardState(this.getMainCard().getId());
if (mainCardState != null
if (this.getSpellAbility() != null // lands can't be casted (haven't spell ability), so ignore it
&& mainCardState != null
&& !mainCardState.hasLostAllAbilities()
&& mainCardState.getAbilities().containsClass(FlashbackAbility.class)) {
FlashbackAbility flash = new FlashbackAbility(this.getManaCost(), this.isInstant() ? TimingRule.INSTANT : TimingRule.SORCERY);
@ -563,13 +521,22 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
stackObject = game.getStack().getSpell(this.getId(), false);
}
if (stackObject == null && (this instanceof SplitCard)) { // handle if half of Split cast is on the stack
// handle half of Split Cards on stack
if (stackObject == null && (this instanceof SplitCard)) {
stackObject = game.getStack().getSpell(((SplitCard) this).getLeftHalfCard().getId(), false);
if (stackObject == null) {
stackObject = game.getStack().getSpell(((SplitCard) this).getRightHalfCard().getId(), false);
}
}
// handle half of Modal Double Faces Cards on stack
if (stackObject == null && (this instanceof ModalDoubleFacesCard)) {
stackObject = game.getStack().getSpell(((ModalDoubleFacesCard) this).getLeftHalfCard().getId(), false);
if (stackObject == null) {
stackObject = game.getStack().getSpell(((ModalDoubleFacesCard) this).getRightHalfCard().getId(), false);
}
}
if (stackObject == null && (this instanceof AdventureCard)) {
stackObject = game.getStack().getSpell(((AdventureCard) this).getSpellCard().getId(), false);
}
@ -687,10 +654,6 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
@Override
public final Card getSecondCardFace() {
// TODO: remove when MDFCs are implemented
if (modalDFC) {
return null;
}
// init second side card on first call
if (secondSideCardClazz == null && secondSideCard == null) {
return null;
@ -726,11 +689,6 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
return flipCardName;
}
@Override
public boolean isSplitCard() {
return splitCard;
}
@Override
public boolean getUsesVariousArt() {
return usesVariousArt;

View file

@ -0,0 +1,281 @@
package mage.cards;
import mage.MageInt;
import mage.MageObject;
import mage.ObjectColor;
import mage.abilities.Abilities;
import mage.abilities.AbilitiesImpl;
import mage.abilities.Ability;
import mage.abilities.SpellAbility;
import mage.abilities.costs.mana.ManaCost;
import mage.abilities.costs.mana.ManaCosts;
import mage.constants.*;
import mage.game.Game;
import mage.game.events.ZoneChangeEvent;
import mage.util.CardUtil;
import mage.util.SubTypeList;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.UUID;
/**
* @author JayDi85
*/
public abstract class ModalDoubleFacesCard extends CardImpl {
protected Card leftHalfCard; // main card in all zone
protected Card rightHalfCard; // second side card, can be only in stack and battlefield zones
public ModalDoubleFacesCard(UUID ownerId, CardSetInfo setInfo,
CardType[] typesLeft, SubType[] subTypesLeft, String costsLeft,
String secondSideName, CardType[] typesRight, SubType[] subTypesRight, String costsRight) {
super(ownerId, setInfo, typesLeft, costsLeft + costsRight, SpellAbilityType.MODAL);
// main card name must be same as left side
leftHalfCard = new ModalDoubleFacesCardHalfImpl(this.getOwnerId(), new CardSetInfo(setInfo.getName(), setInfo.getExpansionSetCode(), setInfo.getCardNumber(), setInfo.getRarity(), setInfo.getGraphicInfo()),
typesLeft, subTypesLeft, costsLeft, this, SpellAbilityType.MODAL_LEFT);
rightHalfCard = new ModalDoubleFacesCardHalfImpl(this.getOwnerId(), new CardSetInfo(secondSideName, setInfo.getExpansionSetCode(), setInfo.getCardNumber(), setInfo.getRarity(), setInfo.getGraphicInfo()),
typesRight, subTypesRight, costsRight, this, SpellAbilityType.MODAL_RIGHT);
}
public ModalDoubleFacesCard(ModalDoubleFacesCard card) {
super(card);
this.leftHalfCard = card.getLeftHalfCard().copy();
((ModalDoubleFacesCardHalf) leftHalfCard).setParentCard(this);
this.rightHalfCard = card.rightHalfCard.copy();
((ModalDoubleFacesCardHalf) rightHalfCard).setParentCard(this);
}
public ModalDoubleFacesCardHalf getLeftHalfCard() {
return (ModalDoubleFacesCardHalf) leftHalfCard;
}
public ModalDoubleFacesCardHalf getRightHalfCard() {
return (ModalDoubleFacesCardHalf) rightHalfCard;
}
@Override
public void assignNewId() {
super.assignNewId();
leftHalfCard.assignNewId();
rightHalfCard.assignNewId();
}
@Override
public void setCopy(boolean isCopy, MageObject copiedFrom) {
super.setCopy(isCopy, copiedFrom);
leftHalfCard.setCopy(isCopy, copiedFrom);
rightHalfCard.setCopy(isCopy, copiedFrom);
}
@Override
public boolean moveToZone(Zone toZone, UUID sourceId, Game game, boolean flag, List<UUID> appliedEffects) {
if (super.moveToZone(toZone, sourceId, game, flag, appliedEffects)) {
game.getState().setZone(leftHalfCard.getId(), toZone);
game.getState().setZone(rightHalfCard.getId(), toZone);
return true;
}
return false;
}
@Override
public void setZone(Zone zone, Game game) {
super.setZone(zone, game);
game.setZone(leftHalfCard.getId(), zone);
game.setZone(rightHalfCard.getId(), zone);
}
@Override
public boolean moveToExile(UUID exileId, String name, UUID sourceId, Game game, List<UUID> appliedEffects) {
if (super.moveToExile(exileId, name, sourceId, game, appliedEffects)) {
Zone currentZone = game.getState().getZone(getId());
game.getState().setZone(leftHalfCard.getId(), currentZone);
game.getState().setZone(rightHalfCard.getId(), currentZone);
return true;
}
return false;
}
@Override
public boolean removeFromZone(Game game, Zone fromZone, UUID sourceId) {
// zone contains only one main card
return super.removeFromZone(game, fromZone, sourceId);
}
@Override
public void updateZoneChangeCounter(Game game, ZoneChangeEvent event) {
if (isCopy()) { // same as meld cards
super.updateZoneChangeCounter(game, event);
return;
}
super.updateZoneChangeCounter(game, event);
leftHalfCard.updateZoneChangeCounter(game, event);
rightHalfCard.updateZoneChangeCounter(game, event);
}
@Override
public boolean cast(Game game, Zone fromZone, SpellAbility ability, UUID controllerId) {
switch (ability.getSpellAbilityType()) {
case MODAL_LEFT:
return this.leftHalfCard.cast(game, fromZone, ability, controllerId);
case MODAL_RIGHT:
return this.rightHalfCard.cast(game, fromZone, ability, controllerId);
default:
if (this.leftHalfCard.getSpellAbility() != null)
this.leftHalfCard.getSpellAbility().setControllerId(controllerId);
if (this.rightHalfCard.getSpellAbility() != null)
this.rightHalfCard.getSpellAbility().setControllerId(controllerId);
return super.cast(game, fromZone, ability, controllerId);
}
}
@Override
public ArrayList<CardType> getCardType() {
// CardImpl's constructor can call some code on init, so you must check left/right before
// it's a bad workaround
return leftHalfCard != null ? leftHalfCard.getCardType() : cardType;
}
@Override
public SubTypeList getSubtype(Game game) {
// rules: While a double-faced card isnt on the stack or battlefield, consider only the characteristics of its front face.
// CardImpl's constructor can call some code on init, so you must check left/right before
return leftHalfCard != null ? leftHalfCard.getSubtype(game) : subtype;
}
@Override
public boolean hasSubtype(SubType subtype, Game game) {
return leftHalfCard.hasSubtype(subtype, game);
}
@Override
public EnumSet<SuperType> getSuperType() {
return EnumSet.noneOf(SuperType.class);
}
@Override
public Abilities<Ability> getAbilities() {
return getInnerAbilities(false);
}
public Abilities<Ability> getSharedAbilities(Game game) {
// no shared abilities for mdf cards (e.g. must be left or right only)
return new AbilitiesImpl<>();
}
@Override
public Abilities<Ability> getAbilities(Game game) {
return getInnerAbilities(game, false);
}
private Abilities<Ability> getInnerAbilities(Game game, boolean showOnlyMainSide) {
Abilities<Ability> allAbilites = new AbilitiesImpl<>();
// ignore default spell ability from main card (only halfes are actual)
for (Ability ability : super.getAbilities(game)) {
if (ability instanceof SpellAbility && ((SpellAbility) ability).getSpellAbilityType() == SpellAbilityType.MODAL) {
continue;
}
allAbilites.add(ability);
}
allAbilites.addAll(leftHalfCard.getAbilities(game));
if (!showOnlyMainSide) {
allAbilites.addAll(rightHalfCard.getAbilities(game));
}
return allAbilites;
}
private Abilities<Ability> getInnerAbilities(boolean showOnlyMainSide) {
Abilities<Ability> allAbilites = new AbilitiesImpl<>();
// ignore default spell ability from main card (only halfes are actual)
for (Ability ability : super.getAbilities()) {
if (ability instanceof SpellAbility && ((SpellAbility) ability).getSpellAbilityType() == SpellAbilityType.MODAL) {
continue;
}
allAbilites.add(ability);
}
allAbilites.addAll(leftHalfCard.getAbilities());
if (!showOnlyMainSide) {
allAbilites.addAll(rightHalfCard.getAbilities());
}
return allAbilites;
}
@Override
public List<String> getRules() {
// rules must show only main side (another side visible by toggle/transform button in GUI)
// card hints from both sides
return CardUtil.getCardRulesWithAdditionalInfo(this.getId(), this.getName(),
this.getInnerAbilities(true), this.getInnerAbilities(false)
);
}
@Override
public List<String> getRules(Game game) {
// rules must show only main side (another side visible by toggle/transform button in GUI)
// card hints from both sides
return CardUtil.getCardRulesWithAdditionalInfo(game, this.getId(), this.getName(),
this.getInnerAbilities(game, true), this.getInnerAbilities(game, false)
);
}
@Override
public boolean hasAbility(Ability ability, Game game) {
return super.hasAbility(ability, game);
}
@Override
public ObjectColor getColor(Game game) {
return leftHalfCard.getColor(game);
}
@Override
public ObjectColor getFrameColor(Game game) {
return leftHalfCard.getFrameColor(game);
}
@Override
public void setOwnerId(UUID ownerId) {
super.setOwnerId(ownerId);
abilities.setControllerId(ownerId);
leftHalfCard.getAbilities().setControllerId(ownerId);
leftHalfCard.setOwnerId(ownerId);
rightHalfCard.getAbilities().setControllerId(ownerId);
rightHalfCard.setOwnerId(ownerId);
}
@Override
public ManaCosts<ManaCost> getManaCost() {
return leftHalfCard.getManaCost();
}
@Override
public int getConvertedManaCost() {
// Rules:
// The converted mana cost of a modal double-faced card is based on the characteristics of the
// face thats being considered. On the stack and battlefield, consider whichever face is up.
// In all other zones, consider only the front face. This is different than how the converted
// mana cost of a transforming double-faced card is determined.
// on stack or battlefield it must be half card with own cost
return leftHalfCard.getConvertedManaCost();
}
@Override
public MageInt getPower() {
return leftHalfCard.getPower();
}
@Override
public MageInt getToughness() {
return leftHalfCard.getToughness();
}
}

View file

@ -0,0 +1,18 @@
package mage.cards;
import mage.MageInt;
/**
* @author JayDi85
*/
public interface ModalDoubleFacesCardHalf extends Card {
@Override
ModalDoubleFacesCardHalf copy();
void setParentCard(ModalDoubleFacesCard card);
ModalDoubleFacesCard getParentCard();
void setPT(MageInt power, MageInt toughtness);
}

View file

@ -0,0 +1,109 @@
package mage.cards;
import mage.MageInt;
import mage.constants.CardType;
import mage.constants.SpellAbilityType;
import mage.constants.SubType;
import mage.constants.Zone;
import mage.game.Game;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
/**
* @author JayDi85
*/
public class ModalDoubleFacesCardHalfImpl extends CardImpl implements ModalDoubleFacesCardHalf {
ModalDoubleFacesCard parentCard;
public ModalDoubleFacesCardHalfImpl(UUID ownerId, CardSetInfo setInfo, CardType[] cardTypes, SubType[] cardSubTypes,
String costs, ModalDoubleFacesCard parentCard, SpellAbilityType spellAbilityType) {
super(ownerId, setInfo, cardTypes, costs, spellAbilityType);
this.subtype.addAll(Arrays.asList(cardSubTypes));
this.parentCard = parentCard;
}
public ModalDoubleFacesCardHalfImpl(final ModalDoubleFacesCardHalfImpl card) {
super(card);
this.parentCard = card.parentCard;
}
@Override
public UUID getOwnerId() {
return parentCard.getOwnerId();
}
@Override
public String getImageName() {
// TODO: own name?
return parentCard.getImageName();
}
@Override
public String getExpansionSetCode() {
// TODO: own set code?
return parentCard.getExpansionSetCode();
}
@Override
public String getCardNumber() {
// TODO: own card number?
return parentCard.getCardNumber();
}
@Override
public boolean moveToZone(Zone toZone, UUID sourceId, Game game, boolean flag, List<UUID> appliedEffects) {
return parentCard.moveToZone(toZone, sourceId, game, flag, appliedEffects);
}
@Override
public boolean moveToExile(UUID exileId, String name, UUID sourceId, Game game, List<UUID> appliedEffects) {
return parentCard.moveToExile(exileId, name, sourceId, game, appliedEffects);
}
@Override
public boolean removeFromZone(Game game, Zone fromZone, UUID sourceId) {
return parentCard.removeFromZone(game, fromZone, sourceId);
}
@Override
public ModalDoubleFacesCard getMainCard() {
return parentCard;
}
@Override
public void setZone(Zone zone, Game game) {
game.setZone(parentCard.getId(), zone);
game.setZone(parentCard.getLeftHalfCard().getId(), zone);
game.setZone(parentCard.getRightHalfCard().getId(), zone);
}
@Override
public ModalDoubleFacesCardHalfImpl copy() {
return new ModalDoubleFacesCardHalfImpl(this);
}
@Override
public void setParentCard(ModalDoubleFacesCard card) {
this.parentCard = card;
}
@Override
public ModalDoubleFacesCard getParentCard() {
return this.parentCard;
}
@Override
public void setPT(MageInt power, MageInt toughness) {
this.power = power;
this.toughness = toughness;
}
@Override
public String getIdName() {
// id must send to main card (popup card hint in game logs)
return getName() + " [" + parentCard.getId().toString().substring(0, 3) + ']';
}
}

View file

@ -32,7 +32,6 @@ public abstract class SplitCard extends CardImpl {
String[] names = setInfo.getName().split(" // ");
leftHalfCard = new SplitCardHalfImpl(this.getOwnerId(), new CardSetInfo(names[0], setInfo.getExpansionSetCode(), setInfo.getCardNumber(), setInfo.getRarity(), setInfo.getGraphicInfo()), typesLeft, costsLeft, this, SpellAbilityType.SPLIT_LEFT);
rightHalfCard = new SplitCardHalfImpl(this.getOwnerId(), new CardSetInfo(names[1], setInfo.getExpansionSetCode(), setInfo.getCardNumber(), setInfo.getRarity(), setInfo.getGraphicInfo()), typesRight, costsRight, this, SpellAbilityType.SPLIT_RIGHT);
this.splitCard = true;
}
public SplitCard(SplitCard card) {
@ -187,4 +186,15 @@ public abstract class SplitCard extends CardImpl {
rightHalfCard.getAbilities().setControllerId(ownerId);
rightHalfCard.setOwnerId(ownerId);
}
@Override
public int getConvertedManaCost() {
// 202.3d The converted mana cost of a split card not on the stack or of a fused split spell on the
// stack is determined from the combined mana costs of its halves. Otherwise, while a split card is
// on the stack, the converted mana cost of the spell is determined by the mana cost of the half
// that was chosen to be cast. See rule 708, Split Cards.
// split card and it's halfes contains own mana costs, so no need to rewrite logic
return super.getConvertedManaCost();
}
}

View file

@ -1,12 +1,6 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package mage.cards;
/**
*
* @author LevelX2
*/
public interface SplitCardHalf extends Card {

View file

@ -1,8 +1,3 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package mage.cards;
import mage.constants.CardType;
@ -14,7 +9,6 @@ import java.util.List;
import java.util.UUID;
/**
*
* @author LevelX2
*/
public class SplitCardHalfImpl extends CardImpl implements SplitCardHalf {
@ -61,6 +55,11 @@ public class SplitCardHalfImpl extends CardImpl implements SplitCardHalf {
return splitCardParent.moveToExile(exileId, name, sourceId, game, appliedEffects);
}
@Override
public boolean removeFromZone(Game game, Zone fromZone, UUID sourceId) {
return splitCardParent.removeFromZone(game, fromZone, sourceId);
}
@Override
public SplitCard getMainCard() {
return splitCardParent;

View file

@ -6,6 +6,7 @@ import mage.abilities.costs.mana.ManaCost;
import mage.abilities.costs.mana.ManaCosts;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.cards.CardImpl;
import mage.cards.ModalDoubleFacesCard;
import mage.cards.repository.CardInfo;
import mage.cards.repository.CardRepository;
import org.apache.log4j.Logger;
@ -20,6 +21,7 @@ import java.util.List;
public class MockCard extends CardImpl {
static public String ADVENTURE_NAME_SEPARATOR = " // ";
static public String MODAL_DOUBLE_FACES_NAME_SEPARATOR = " // ";
// Needs to be here, as it is normally calculated from the
// PlaneswalkerEntersWithLoyaltyAbility of the card... but the MockCard
@ -30,6 +32,7 @@ public class MockCard extends CardImpl {
protected ManaCosts<ManaCost> manaCostLeft;
protected ManaCosts<ManaCost> manaCostRight;
protected String adventureSpellName;
protected boolean isModalDoubleFacesCard;
public MockCard(CardInfo card) {
super(null, card.getName());
@ -53,7 +56,6 @@ public class MockCard extends CardImpl {
this.frameColor = card.getFrameColor();
this.frameStyle = card.getFrameStyle();
this.splitCard = card.isSplitCard();
this.flipCard = card.isFlipCard();
this.transformable = card.isDoubleFaced();
@ -66,10 +68,16 @@ public class MockCard extends CardImpl {
this.adventureSpellName = card.getAdventureSpellName();
}
if (card.isModalDoubleFacesCard()) {
ModalDoubleFacesCard mdfCard = (ModalDoubleFacesCard) card.getCard();
CardInfo mdfSecondSide = new CardInfo(mdfCard.getRightHalfCard());
this.secondSideCard = new MockCard(mdfSecondSide);
this.isModalDoubleFacesCard = true;
}
if (this.isPlaneswalker()) {
String startingLoyaltyString = card.getStartingLoyalty();
if (startingLoyaltyString.isEmpty()) {
//Logger.getLogger(MockCard.class).warn("Planeswalker `" + this.name + "` has empty starting loyalty.");
} else {
try {
this.startingLoyalty = Integer.parseInt(startingLoyaltyString);
@ -117,8 +125,14 @@ public class MockCard extends CardImpl {
}
public String getFullName(boolean showSecondName) {
if (!showSecondName) {
return getName();
}
if (adventureSpellName != null) {
return getName() + ADVENTURE_NAME_SEPARATOR + adventureSpellName;
} else if (isModalDoubleFacesCard) {
return getName() + MODAL_DOUBLE_FACES_NAME_SEPARATOR + this.secondSideCard.getName();
} else {
return getName();
}
@ -145,4 +159,10 @@ public class MockCard extends CardImpl {
private Ability textAbilityFromString(final String text) {
return new MockAbility(text);
}
@Override
public boolean isTransformable() {
// must enable toggle mode in deck editor (switch between card sides);
return super.isTransformable() || this.isModalDoubleFacesCard;
}
}

View file

@ -35,7 +35,6 @@ public class MockSplitCard extends SplitCard {
this.usesVariousArt = card.usesVariousArt();
this.color = card.getColor();
this.splitCard = card.isSplitCard();
this.flipCard = card.isFlipCard();
this.transformable = card.isDoubleFaced();

View file

@ -3,8 +3,6 @@ package mage.cards.repository;
import com.j256.ormlite.field.DataType;
import com.j256.ormlite.field.DatabaseField;
import com.j256.ormlite.table.DatabaseTable;
import java.util.*;
import java.util.stream.Collectors;
import mage.ObjectColor;
import mage.abilities.Ability;
import mage.abilities.SpellAbility;
@ -17,6 +15,9 @@ import mage.util.CardUtil;
import mage.util.SubTypeList;
import org.apache.log4j.Logger;
import java.util.*;
import java.util.stream.Collectors;
/**
* @author North
*/
@ -103,6 +104,12 @@ public class CardInfo {
protected boolean adventureCard;
@DatabaseField
protected String adventureSpellName;
@DatabaseField
protected boolean modalDoubleFacesCard;
@DatabaseField
protected String modalDoubleFacesSecondSideName;
// if you add new field with card side name then update CardRepository.addNewNames too
public enum ManaCostSide {
LEFT, RIGHT, ALL
@ -121,7 +128,7 @@ public class CardInfo {
this.toughness = card.getToughness().toString();
this.convertedManaCost = card.getConvertedManaCost();
this.rarity = card.getRarity();
this.splitCard = card.isSplitCard();
this.splitCard = card instanceof SplitCard;
this.splitCardFuse = card.getSpellAbility() != null && card.getSpellAbility().getSpellAbilityType() == SpellAbilityType.SPLIT_FUSED;
this.splitCardAftermath = card.getSpellAbility() != null && card.getSpellAbility().getSpellAbilityType() == SpellAbilityType.SPLIT_AFTERMATH;
@ -140,6 +147,11 @@ public class CardInfo {
this.adventureSpellName = ((AdventureCard) card).getSpellCard().getName();
}
if (card instanceof ModalDoubleFacesCard) {
this.modalDoubleFacesCard = true;
this.modalDoubleFacesSecondSideName = ((ModalDoubleFacesCard) card).getRightHalfCard().getName();
}
this.frameStyle = card.getFrameStyle().toString();
this.frameColor = card.getFrameColor(null).toString();
this.variousArt = card.getUsesVariousArt();
@ -153,13 +165,17 @@ public class CardInfo {
this.setSubtypes(card.getSubtype(null).stream().map(SubType::toString).collect(Collectors.toList()));
this.setSuperTypes(card.getSuperType());
// mana cost can contains multiple cards (split left/right, card/adventure)
// mana cost can contains multiple cards (split left/right, modal double faces, card/adventure)
if (card instanceof SplitCard) {
List<String> manaCostLeft = ((SplitCard) card).getLeftHalfCard().getManaCost().getSymbols();
List<String> manaCostRight = ((SplitCard) card).getRightHalfCard().getManaCost().getSymbols();
this.setManaCosts(CardUtil.concatManaSymbols(SPLIT_MANA_SEPARATOR_FULL, manaCostLeft, manaCostRight));
} else if (card instanceof ModalDoubleFacesCard) {
List<String> manaCostLeft = ((ModalDoubleFacesCard) card).getLeftHalfCard().getManaCost().getSymbols();
List<String> manaCostRight = ((ModalDoubleFacesCard) card).getRightHalfCard().getManaCost().getSymbols();
this.setManaCosts(CardUtil.concatManaSymbols(SPLIT_MANA_SEPARATOR_FULL, manaCostLeft, manaCostRight));
} else if (card instanceof AdventureCard) {
List<String> manaCostLeft = ((AdventureCard) card).getSpellCard().getManaCost().getSymbols(); // Spell from left like MTGA
List<String> manaCostLeft = ((AdventureCard) card).getSpellCard().getManaCost().getSymbols();
List<String> manaCostRight = card.getManaCost().getSymbols();
this.setManaCosts(CardUtil.concatManaSymbols(SPLIT_MANA_SEPARATOR_FULL, manaCostLeft, manaCostRight));
} else {
@ -181,6 +197,12 @@ public class CardInfo {
length += rule.length();
rulesList.add(rule);
}
} else if (card instanceof ModalDoubleFacesCard) {
// mdf card return main side's rules only (GUI can toggle it to another side)
for (String rule : card.getRules()) {
length += rule.length();
rulesList.add(rule);
}
} else {
for (String rule : card.getRules()) {
length += rule.length();
@ -222,7 +244,6 @@ public class CardInfo {
}
}
if (this.startingLoyalty == null) {
//Logger.getLogger(CardInfo.class).warn("Planeswalker `" + card.getName() + "` missing starting loyalty");
this.startingLoyalty = "";
}
} else {
@ -447,4 +468,12 @@ public class CardInfo {
public String getAdventureSpellName() {
return adventureSpellName;
}
public boolean isModalDoubleFacesCard() {
return modalDoubleFacesCard;
}
public String getModalDoubleFacesSecondSideName() {
return modalDoubleFacesSecondSideName;
}
}

View file

@ -34,9 +34,9 @@ public enum CardRepository {
private static final String JDBC_URL = "jdbc:h2:file:./db/cards.h2;AUTO_SERVER=TRUE";
private static final String VERSION_ENTITY_NAME = "card";
// raise this if db structure was changed
private static final long CARD_DB_VERSION = 52;
private static final long CARD_DB_VERSION = 53;
// raise this if new cards were added to the server
private static final long CARD_CONTENT_VERSION = 232;
private static final long CARD_CONTENT_VERSION = 233;
private Dao<CardInfo, Object> cardDao;
private Set<String> classNames;
private final RepositoryEventSource eventSource = new RepositoryEventSource();
@ -136,16 +136,10 @@ public enum CardRepository {
Set<String> names = new TreeSet<>();
try {
QueryBuilder<CardInfo, Object> qb = cardDao.queryBuilder();
qb.distinct().selectColumns("name");
qb.distinct().selectColumns("name", "modalDoubleFacesSecondSideName", "secondSideName", "flipCardName");
List<CardInfo> results = cardDao.query(qb.prepare());
for (CardInfo card : results) {
int result = card.getName().indexOf(" // ");
if (result > 0) {
names.add(card.getName().substring(0, result));
names.add(card.getName().substring(result + 4));
} else {
names.add(card.getName());
}
addNewNames(card, names);
}
} catch (SQLException ex) {
Logger.getLogger(CardRepository.class).error("Error getting names from DB : " + ex);
@ -153,21 +147,39 @@ public enum CardRepository {
return names;
}
private void addNewNames(CardInfo card, Set<String> namesList) {
// require before call: qb.distinct().selectColumns("name", "modalDoubleFacesSecondSideName"...);
// normal names
int result = card.getName().indexOf(" // ");
if (result > 0) {
namesList.add(card.getName().substring(0, result));
namesList.add(card.getName().substring(result + 4));
} else {
namesList.add(card.getName());
}
// additional names from double side cards
if (card.getSecondSideName() != null && !card.getSecondSideName().isEmpty()) {
namesList.add(card.getSecondSideName());
}
if (card.getModalDoubleFacesSecondSideName() != null && !card.getModalDoubleFacesSecondSideName().isEmpty()) {
namesList.add(card.getModalDoubleFacesSecondSideName());
}
if (card.getFlipCardName() != null && !card.getFlipCardName().isEmpty()) {
namesList.add(card.getFlipCardName());
}
}
public Set<String> getNonLandNames() {
Set<String> names = new TreeSet<>();
try {
QueryBuilder<CardInfo, Object> qb = cardDao.queryBuilder();
qb.distinct().selectColumns("name");
qb.distinct().selectColumns("name", "modalDoubleFacesSecondSideName", "secondSideName", "flipCardName");
qb.where().not().like("types", new SelectArg('%' + CardType.LAND.name() + '%'));
List<CardInfo> results = cardDao.query(qb.prepare());
for (CardInfo card : results) {
int result = card.getName().indexOf(" // ");
if (result > 0) {
names.add(card.getName().substring(0, result));
names.add(card.getName().substring(result + 4));
} else {
names.add(card.getName());
}
addNewNames(card, names);
}
} catch (SQLException ex) {
Logger.getLogger(CardRepository.class).error("Error getting non-land names from DB : " + ex);
@ -188,7 +200,7 @@ public enum CardRepository {
Set<String> names = new TreeSet<>();
try {
QueryBuilder<CardInfo, Object> qb = cardDao.queryBuilder();
qb.distinct().selectColumns("name");
qb.distinct().selectColumns("name", "modalDoubleFacesSecondSideName", "secondSideName", "flipCardName");
Where where = qb.where();
where.and(
where.not().like("supertypes", '%' + SuperType.BASIC.name() + '%'),
@ -196,13 +208,7 @@ public enum CardRepository {
);
List<CardInfo> results = cardDao.query(qb.prepare());
for (CardInfo card : results) {
int result = card.getName().indexOf(" // ");
if (result > 0) {
names.add(card.getName().substring(0, result));
names.add(card.getName().substring(result + 4));
} else {
names.add(card.getName());
}
addNewNames(card, names);
}
} catch (SQLException ex) {
Logger.getLogger(CardRepository.class).error("Error getting non-land names from DB : " + ex);
@ -215,17 +221,11 @@ public enum CardRepository {
Set<String> names = new TreeSet<>();
try {
QueryBuilder<CardInfo, Object> qb = cardDao.queryBuilder();
qb.distinct().selectColumns("name");
qb.distinct().selectColumns("name", "modalDoubleFacesSecondSideName", "secondSideName", "flipCardName");
qb.where().not().like("supertypes", new SelectArg('%' + SuperType.BASIC.name() + '%'));
List<CardInfo> results = cardDao.query(qb.prepare());
for (CardInfo card : results) {
int result = card.getName().indexOf(" // ");
if (result > 0) {
names.add(card.getName().substring(0, result));
names.add(card.getName().substring(result + 4));
} else {
names.add(card.getName());
}
addNewNames(card, names);
}
} catch (SQLException ex) {
Logger.getLogger(CardRepository.class).error("Error getting non-land names from DB : " + ex);
@ -238,17 +238,11 @@ public enum CardRepository {
Set<String> names = new TreeSet<>();
try {
QueryBuilder<CardInfo, Object> qb = cardDao.queryBuilder();
qb.distinct().selectColumns("name");
qb.distinct().selectColumns("name", "modalDoubleFacesSecondSideName", "secondSideName", "flipCardName");
qb.where().like("types", new SelectArg('%' + CardType.CREATURE.name() + '%'));
List<CardInfo> results = cardDao.query(qb.prepare());
for (CardInfo card : results) {
int result = card.getName().indexOf(" // ");
if (result > 0) {
names.add(card.getName().substring(0, result));
names.add(card.getName().substring(result + 4));
} else {
names.add(card.getName());
}
addNewNames(card, names);
}
} catch (SQLException ex) {
Logger.getLogger(CardRepository.class).error("Error getting creature names from DB : " + ex);
@ -261,17 +255,11 @@ public enum CardRepository {
Set<String> names = new TreeSet<>();
try {
QueryBuilder<CardInfo, Object> qb = cardDao.queryBuilder();
qb.distinct().selectColumns("name");
qb.distinct().selectColumns("name", "modalDoubleFacesSecondSideName", "secondSideName", "flipCardName");
qb.where().like("types", new SelectArg('%' + CardType.ARTIFACT.name() + '%'));
List<CardInfo> results = cardDao.query(qb.prepare());
for (CardInfo card : results) {
int result = card.getName().indexOf(" // ");
if (result > 0) {
names.add(card.getName().substring(0, result));
names.add(card.getName().substring(result + 4));
} else {
names.add(card.getName());
}
addNewNames(card, names);
}
} catch (SQLException ex) {
Logger.getLogger(CardRepository.class).error("Error getting artifact names from DB : " + ex);
@ -284,7 +272,7 @@ public enum CardRepository {
Set<String> names = new TreeSet<>();
try {
QueryBuilder<CardInfo, Object> qb = cardDao.queryBuilder();
qb.distinct().selectColumns("name");
qb.distinct().selectColumns("name", "modalDoubleFacesSecondSideName", "secondSideName", "flipCardName");
Where where = qb.where();
where.and(
where.not().like("types", '%' + CardType.CREATURE.name() + '%'),
@ -292,13 +280,7 @@ public enum CardRepository {
);
List<CardInfo> results = cardDao.query(qb.prepare());
for (CardInfo card : results) {
int result = card.getName().indexOf(" // ");
if (result > 0) {
names.add(card.getName().substring(0, result));
names.add(card.getName().substring(result + 4));
} else {
names.add(card.getName());
}
addNewNames(card, names);
}
} catch (SQLException ex) {
Logger.getLogger(CardRepository.class).error("Error getting non-land and non-creature names from DB : " + ex);
@ -310,7 +292,7 @@ public enum CardRepository {
Set<String> names = new TreeSet<>();
try {
QueryBuilder<CardInfo, Object> qb = cardDao.queryBuilder();
qb.distinct().selectColumns("name");
qb.distinct().selectColumns("name", "modalDoubleFacesSecondSideName", "secondSideName", "flipCardName");
Where where = qb.where();
where.and(
where.not().like("types", '%' + CardType.ARTIFACT.name() + '%'),
@ -318,13 +300,7 @@ public enum CardRepository {
);
List<CardInfo> results = cardDao.query(qb.prepare());
for (CardInfo card : results) {
int result = card.getName().indexOf(" // ");
if (result > 0) {
names.add(card.getName().substring(0, result));
names.add(card.getName().substring(result + 4));
} else {
names.add(card.getName());
}
addNewNames(card, names);
}
} catch (SQLException ex) {
Logger.getLogger(CardRepository.class).error("Error getting non-artifact non-land names from DB : " + ex);

View file

@ -59,7 +59,7 @@ public final class CardScanner {
new CardSetInfo(setInfo.getName(), set.getCode(), setInfo.getCardNumber(), setInfo.getRarity(), setInfo.getGraphicInfo()),
errorsList);
if (card != null) {
cardsToAdd.add(new CardInfo(card));
cardsToAdd.add(new CardInfo(card)); // normal, transformed, adventure, modal double faces -- all must have single face in db
if (card instanceof SplitCard) {
SplitCard splitCard = (SplitCard) card;
cardsToAdd.add(new CardInfo(splitCard.getLeftHalfCard()));

View file

@ -1,7 +1,6 @@
package mage.constants;
/**
*
* @author North
*/
public enum SpellAbilityType {
@ -13,7 +12,9 @@ public enum SpellAbilityType {
SPLIT_FUSED("Split SpellAbility"),
SPLIT_LEFT("LeftSplit SpellAbility"),
SPLIT_RIGHT("RightSplit SpellAbility"),
MODE("Mode SpellAbility"),
MODAL("Modal SpellAbility"), // used for modal double faces cards
MODAL_LEFT("LeftModal SpellAbility"),
MODAL_RIGHT("RightModal SpellAbility"),
SPLICE("Spliced SpellAbility"),
ADVENTURE_SPELL("Adventure SpellAbility");

View file

@ -3,11 +3,8 @@ package mage.constants;
import mage.MageObject;
import mage.filter.predicate.Predicate;
import mage.game.Game;
import mage.util.SubTypeList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.Set;
import java.util.*;
import java.util.stream.Collectors;
public enum SubType {
@ -468,6 +465,22 @@ public enum SubType {
}
}
private static final Set<SubType> landTypes = new HashSet<>();
private static final Map<SubTypeSet, Set<SubType>> subTypeSetMap = new HashMap<>();
static {
for (SubTypeSet subTypeSet : SubTypeSet.values()) {
subTypeSetMap.put(
subTypeSet,
Arrays.stream(values())
.filter(subType -> subType.getSubTypeSet() == subTypeSet)
.collect(Collectors.toSet())
);
}
landTypes.addAll(subTypeSetMap.get(SubTypeSet.BasicLandType));
landTypes.addAll(subTypeSetMap.get(SubTypeSet.NonBasicLandType));
}
private final SubTypeSet subTypeSet;
private final String description;
private final boolean customSet;
@ -534,49 +547,46 @@ public enum SubType {
return subTypeSet;
}
public static Set<SubType> getArtifactTypes() {
Set<SubType> subTypes = EnumSet.noneOf(SubType.class);
for (SubType subType : values()) {
if (subType.getSubTypeSet() == SubTypeSet.ArtifactType) {
subTypes.add(subType);
}
public boolean canGain(MageObject mageObject) {
switch (subTypeSet) {
case CreatureType:
return mageObject.isCreature() || mageObject.isTribal();
case BasicLandType:
case NonBasicLandType:
return mageObject.isLand();
case EnchantmentType:
return mageObject.isEnchantment();
case ArtifactType:
return mageObject.isArtifact();
case PlaneswalkerType:
return mageObject.isPlaneswalker();
}
return subTypes;
return false;
}
public static Set<SubType> getArtifactTypes() {
return subTypeSetMap.get(SubTypeSet.ArtifactType);
}
public static Set<SubType> getEnchantmentTypes() {
return subTypeSetMap.get(SubTypeSet.EnchantmentType);
}
public static Set<SubType> getPlaneswalkerTypes() {
Set<SubType> subTypes = EnumSet.noneOf(SubType.class);
for (SubType subType : values()) {
if (subType.getSubTypeSet() == SubTypeSet.PlaneswalkerType) {
subTypes.add(subType);
}
}
return subTypes;
return subTypeSetMap.get(SubTypeSet.PlaneswalkerType);
}
public static Set<SubType> getCreatureTypes() {
Set<SubType> subTypes = EnumSet.noneOf(SubType.class);
for (SubType subType : values()) {
if (subType.getSubTypeSet() == SubTypeSet.CreatureType) {
subTypes.add(subType);
}
}
return subTypes;
return subTypeSetMap.get(SubTypeSet.CreatureType);
}
public static Set<SubType> getBasicLands() {
return Arrays.stream(values())
.filter(p -> p.getSubTypeSet() == SubTypeSet.BasicLandType)
.collect(Collectors.toSet());
return subTypeSetMap.get(SubTypeSet.BasicLandType);
}
public static SubTypeList getLandTypes() {
SubTypeList landTypes = new SubTypeList();
for (SubType subType : values()) {
if (subType.getSubTypeSet() == SubTypeSet.BasicLandType || subType.getSubTypeSet() == SubTypeSet.NonBasicLandType) {
landTypes.add(subType);
}
}
public static Set<SubType> getLandTypes() {
return landTypes;
}
}

View file

@ -12,6 +12,7 @@ import mage.game.Game;
*/
public enum CounterType {
AEGIS("aegis"),
AGE("age"),
AIM("aim"),
ARROW("arrow"),

View file

@ -1,7 +1,7 @@
package mage.filter.predicate.mageobject;
import mage.MageObject;
import mage.cards.ModalDoubleFacesCardHalf;
import mage.cards.SplitCardHalf;
import mage.constants.Zone;
import mage.filter.predicate.Predicate;
@ -18,8 +18,13 @@ public enum MulticoloredPredicate implements Predicate<MageObject> {
// 708.3. Each split card that consists of two halves with different colored mana symbols in their mana costs
// is a multicolored card while it's not a spell on the stack. While it's a spell on the stack, it's only the
// color or colors of the half or halves being cast. #
if (input instanceof SplitCardHalf && game.getState().getZone(input.getId()) != Zone.STACK) {
if (input instanceof SplitCardHalf
&& game.getState().getZone(input.getId()) != Zone.STACK) {
return 1 < ((SplitCardHalf) input).getMainCard().getColor(game).getColorCount();
} else if (input instanceof ModalDoubleFacesCardHalf
&& (game.getState().getZone(input.getId()) != Zone.STACK && game.getState().getZone(input.getId()) != Zone.BATTLEFIELD)) {
// While a double-faced card isnt on the stack or battlefield, consider only the characteristics of its front face.
return 1 < ((ModalDoubleFacesCardHalf) input).getMainCard().getColor(game).getColorCount();
} else {
return 1 < input.getColor(game).getColorCount();
}

View file

@ -1,6 +1,7 @@
package mage.filter.predicate.mageobject;
import mage.MageObject;
import mage.cards.ModalDoubleFacesCard;
import mage.cards.SplitCard;
import mage.constants.SpellAbilityType;
import mage.filter.predicate.Predicate;
@ -32,10 +33,15 @@ public class NamePredicate implements Predicate<MageObject> {
}
// If a player names a card, the player may name either half of a split card, but not both.
// A split card has the chosen name if one of its two names matches the chosen name.
// Same for modal double faces cards
if (input instanceof SplitCard) {
return CardUtil.haveSameNames(name, ((SplitCard) input).getLeftHalfCard().getName(), this.ignoreMtgRuleForEmptyNames) ||
CardUtil.haveSameNames(name, ((SplitCard) input).getRightHalfCard().getName(), this.ignoreMtgRuleForEmptyNames) ||
CardUtil.haveSameNames(name, input.getName(), this.ignoreMtgRuleForEmptyNames);
} else if (input instanceof ModalDoubleFacesCard) {
return CardUtil.haveSameNames(name, ((ModalDoubleFacesCard) input).getLeftHalfCard().getName(), this.ignoreMtgRuleForEmptyNames) ||
CardUtil.haveSameNames(name, ((ModalDoubleFacesCard) input).getRightHalfCard().getName(), this.ignoreMtgRuleForEmptyNames) ||
CardUtil.haveSameNames(name, input.getName(), this.ignoreMtgRuleForEmptyNames);
} else if (input instanceof Spell && ((Spell) input).getSpellAbility().getSpellAbilityType() == SpellAbilityType.SPLIT_FUSED) {
SplitCard card = (SplitCard) ((Spell) input).getCard();
return CardUtil.haveSameNames(name, card.getLeftHalfCard().getName(), this.ignoreMtgRuleForEmptyNames) ||

View file

@ -2,6 +2,7 @@ package mage.filter.predicate.other;
import mage.cards.AdventureCard;
import mage.cards.Card;
import mage.cards.ModalDoubleFacesCard;
import mage.cards.SplitCard;
import mage.cards.mock.MockCard;
import mage.constants.SubType;
@ -52,6 +53,8 @@ public class CardTextPredicate implements Predicate<Card> {
String fullName = input.getName();
if (input instanceof MockCard) {
fullName = ((MockCard) input).getFullName(true);
} else if (input instanceof ModalDoubleFacesCard) {
fullName = input.getName() + MockCard.MODAL_DOUBLE_FACES_NAME_SEPARATOR + ((ModalDoubleFacesCard) input).getRightHalfCard().getName();
} else if (input instanceof AdventureCard) {
fullName = input.getName() + MockCard.ADVENTURE_NAME_SEPARATOR + ((AdventureCard) input).getSpellCard().getName();
}
@ -67,14 +70,14 @@ public class CardTextPredicate implements Predicate<Card> {
}
}
//separate by spaces
// separate by spaces
String[] tokens = text.toLowerCase(Locale.ENGLISH).split(" ");
for (String token : tokens) {
boolean found = false;
if (!token.isEmpty()) {
// then try to find in rules
if (inRules) {
if (input.isSplitCard()) {
if (input instanceof SplitCard) {
for (String rule : ((SplitCard) input).getLeftHalfCard().getRules(game)) {
if (rule.toLowerCase(Locale.ENGLISH).contains(token)) {
found = true;
@ -88,6 +91,22 @@ public class CardTextPredicate implements Predicate<Card> {
}
}
}
if (input instanceof ModalDoubleFacesCard) {
for (String rule : ((ModalDoubleFacesCard) input).getLeftHalfCard().getRules(game)) {
if (rule.toLowerCase(Locale.ENGLISH).contains(token)) {
found = true;
break;
}
}
for (String rule : ((ModalDoubleFacesCard) input).getRightHalfCard().getRules(game)) {
if (rule.toLowerCase(Locale.ENGLISH).contains(token)) {
found = true;
break;
}
}
}
if (input instanceof AdventureCard) {
for (String rule : ((AdventureCard) input).getSpellCard().getRules(game)) {
if (rule.toLowerCase(Locale.ENGLISH).contains(token)) {
@ -96,6 +115,7 @@ public class CardTextPredicate implements Predicate<Card> {
}
}
}
for (String rule : input.getRules(game)) {
if (rule.toLowerCase(Locale.ENGLISH).contains(token)) {
found = true;

View file

@ -1,21 +1,20 @@
package mage.filter.predicate.permanent;
import mage.MageObject;
import mage.filter.predicate.Predicate;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
/**
* @author LevelX2
*/
public enum CommanderPredicate implements Predicate<Permanent> {
public enum CommanderPredicate implements Predicate<MageObject> {
instance;
@Override
public boolean apply(Permanent input, Game game) {
Player owner = game.getPlayer(input.getOwnerId());
return owner != null
&& game.getCommandersIds(owner).contains(input.getId());
public boolean apply(MageObject input, Game game) {
Player owner = game.getPlayer(game.getOwnerId(input.getId()));
return owner != null && game.getCommandersIds(owner).contains(input.getId());
}
@Override

View file

@ -1,8 +1,3 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package mage.filter.predicate.permanent;
import mage.filter.predicate.Predicate;
@ -10,10 +5,10 @@ import mage.game.Game;
import mage.game.permanent.Permanent;
/**
*
* @author LevelX2
*/
public class EnteredThisTurnPredicate implements Predicate<Permanent> {
public enum EnteredThisTurnPredicate implements Predicate<Permanent> {
instance;
@Override
public boolean apply(Permanent input, Game game) {

View file

@ -244,17 +244,29 @@ public abstract class GameImpl implements Game, Serializable {
card.setOwnerId(ownerId);
gameCards.put(card.getId(), card);
state.addCard(card);
if (card.isSplitCard()) {
if (card instanceof SplitCard) {
// left
Card leftCard = ((SplitCard) card).getLeftHalfCard();
leftCard.setOwnerId(ownerId);
gameCards.put(leftCard.getId(), leftCard);
state.addCard(leftCard);
// right
Card rightCard = ((SplitCard) card).getRightHalfCard();
rightCard.setOwnerId(ownerId);
gameCards.put(rightCard.getId(), rightCard);
state.addCard(rightCard);
}
if (card instanceof AdventureCard) {
} else if (card instanceof ModalDoubleFacesCard) {
// left
Card leftCard = ((ModalDoubleFacesCard) card).getLeftHalfCard();
leftCard.setOwnerId(ownerId);
gameCards.put(leftCard.getId(), leftCard);
state.addCard(leftCard);
// right
Card rightCard = ((ModalDoubleFacesCard) card).getRightHalfCard();
rightCard.setOwnerId(ownerId);
gameCards.put(rightCard.getId(), rightCard);
state.addCard(rightCard);
} else if (card instanceof AdventureCard) {
Card spellCard = ((AdventureCard) card).getSpellCard();
spellCard.setOwnerId(ownerId);
gameCards.put(spellCard.getId(), spellCard);
@ -1911,7 +1923,7 @@ public abstract class GameImpl implements Game, Serializable {
Iterator<Card> copiedCards = this.getState().getCopiedCards().iterator();
while (copiedCards.hasNext()) {
Card card = copiedCards.next();
if (card instanceof SplitCardHalf || card instanceof AdventureCardSpell) {
if (card instanceof SplitCardHalf || card instanceof AdventureCardSpell || card instanceof ModalDoubleFacesCardHalf) {
continue; // only the main card is moves, not the halves (cause halfes is not copied - it uses original card -- TODO: need to fix (bugs with same card copy)?
}
Zone zone = state.getZone(card.getId());

View file

@ -1,9 +1,5 @@
package mage.game;
import java.io.Serializable;
import java.util.*;
import static java.util.Collections.emptyList;
import java.util.stream.Collectors;
import mage.MageObject;
import mage.MageObjectReference;
import mage.abilities.*;
@ -12,6 +8,7 @@ import mage.abilities.effects.ContinuousEffects;
import mage.abilities.effects.Effect;
import mage.cards.AdventureCard;
import mage.cards.Card;
import mage.cards.ModalDoubleFacesCard;
import mage.cards.SplitCard;
import mage.constants.Zone;
import mage.designations.Designation;
@ -40,6 +37,12 @@ import mage.util.ThreadLocalStringBuilder;
import mage.watchers.Watcher;
import mage.watchers.Watchers;
import java.io.Serializable;
import java.util.*;
import java.util.stream.Collectors;
import static java.util.Collections.emptyList;
/**
* @author BetaSteward_at_googlemail.com
* <p>
@ -626,6 +629,7 @@ public class GameState implements Serializable, Copyable<GameState> {
// public void addMessage(String message) {
// this.messages.add(message);
// }
/**
* Returns a list of all players of the game ignoring range or if a player
* has lost or left the game.
@ -799,7 +803,7 @@ public class GameState implements Serializable, Copyable<GameState> {
for (Map.Entry<ZoneChangeData, List<GameEvent>> entry : eventsByKey.entrySet()) {
Set<Card> movedCards = new LinkedHashSet<>();
Set<PermanentToken> movedTokens = new LinkedHashSet<>();
for (Iterator<GameEvent> it = entry.getValue().iterator(); it.hasNext();) {
for (Iterator<GameEvent> it = entry.getValue().iterator(); it.hasNext(); ) {
GameEvent event = it.next();
ZoneChangeEvent castEvent = (ZoneChangeEvent) event;
UUID targetId = castEvent.getTargetId();
@ -835,10 +839,14 @@ public class GameState implements Serializable, Copyable<GameState> {
}
// TODO Watchers?
// TODO Abilities?
if (card.isSplitCard()) {
if (card instanceof SplitCard) {
removeCopiedCard(((SplitCard) card).getLeftHalfCard());
removeCopiedCard(((SplitCard) card).getRightHalfCard());
}
if (card instanceof ModalDoubleFacesCard) {
removeCopiedCard(((ModalDoubleFacesCard) card).getLeftHalfCard());
removeCopiedCard(((ModalDoubleFacesCard) card).getRightHalfCard());
}
if (card instanceof AdventureCard) {
removeCopiedCard(((AdventureCard) card).getSpellCard());
}
@ -1194,21 +1202,34 @@ public class GameState implements Serializable, Copyable<GameState> {
}
public Card copyCard(Card cardToCopy, Ability source, Game game) {
// main card
Card copiedCard = cardToCopy.copy();
copiedCard.assignNewId();
copiedCard.setOwnerId(source.getControllerId());
copiedCard.setCopy(true, cardToCopy);
copiedCards.put(copiedCard.getId(), copiedCard);
addCard(copiedCard);
if (copiedCard.isSplitCard()) {
// other faces
if (copiedCard instanceof SplitCard) {
// left
Card leftCard = ((SplitCard) copiedCard).getLeftHalfCard(); // TODO: must be new ID (bugs with same card copy)?
copiedCards.put(leftCard.getId(), leftCard);
addCard(leftCard);
// right
Card rightCard = ((SplitCard) copiedCard).getRightHalfCard();
copiedCards.put(rightCard.getId(), rightCard);
addCard(rightCard);
}
if (copiedCard instanceof AdventureCard) {
} else if (copiedCard instanceof ModalDoubleFacesCard) {
// left
Card leftCard = ((ModalDoubleFacesCard) copiedCard).getLeftHalfCard(); // TODO: must be new ID (bugs with same card copy)?
copiedCards.put(leftCard.getId(), leftCard);
addCard(leftCard);
// right
Card rightCard = ((ModalDoubleFacesCard) copiedCard).getRightHalfCard();
copiedCards.put(rightCard.getId(), rightCard);
addCard(rightCard);
} else if (copiedCard instanceof AdventureCard) {
Card spellCard = ((AdventureCard) copiedCard).getSpellCard();
copiedCards.put(spellCard.getId(), spellCard);
addCard(spellCard);

View file

@ -1,9 +1,7 @@
package mage.game;
import mage.cards.Card;
import mage.cards.Cards;
import mage.cards.CardsImpl;
import mage.cards.MeldCard;
import mage.abilities.keyword.TransformAbility;
import mage.cards.*;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.filter.FilterCard;
@ -19,7 +17,6 @@ import mage.players.Player;
import mage.target.TargetCard;
import java.util.*;
import mage.abilities.keyword.TransformAbility;
/**
* Created by samuelsandeen on 9/6/16.
@ -54,7 +51,7 @@ public final class ZonesHandler {
public static List<ZoneChangeInfo> moveCards(List<ZoneChangeInfo> zoneChangeInfos, Game game) {
// Handle Unmelded Meld Cards
for (ListIterator<ZoneChangeInfo> itr = zoneChangeInfos.listIterator(); itr.hasNext();) {
for (ListIterator<ZoneChangeInfo> itr = zoneChangeInfos.listIterator(); itr.hasNext(); ) {
ZoneChangeInfo info = itr.next();
MeldCard card = game.getMeldCard(info.event.getTargetId());
// Copies should be handled as normal cards.
@ -101,42 +98,57 @@ public final class ZonesHandler {
ZoneChangeEvent event = info.event;
Zone toZone = event.getToZone();
Card targetCard = getTargetCard(game, event.getTargetId());
Cards cards = null;
// If we're moving a token it shouldn't be put into any zone as an object.
Cards cardsToMove = null; // moving real cards
Cards cardsToUpdate = null; // updating all card's parts
// if we're moving a token it shouldn't be put into any zone as an object.
if (!(targetCard instanceof Permanent) && targetCard != null) {
if (targetCard instanceof MeldCard) {
cards = ((MeldCard) targetCard).getHalves();
// meld/group cards must be independent (use can choose order)
cardsToMove = ((MeldCard) targetCard).getHalves();
cardsToUpdate = cardsToMove;
} else if (targetCard instanceof ModalDoubleFacesCard
|| targetCard instanceof ModalDoubleFacesCardHalf) {
// mdf cards must be moved as single object, but each half must be updated separetly
ModalDoubleFacesCard mdfCard = (ModalDoubleFacesCard) targetCard.getMainCard();
cardsToMove = new CardsImpl(mdfCard);
cardsToUpdate = new CardsImpl(mdfCard);
cardsToUpdate.add(mdfCard.getLeftHalfCard());
cardsToUpdate.add(mdfCard.getRightHalfCard());
} else {
cards = new CardsImpl(targetCard);
cardsToMove = new CardsImpl(targetCard);
cardsToUpdate = cardsToMove;
}
Player owner = game.getPlayer(targetCard.getOwnerId());
switch (toZone) {
case HAND:
for (Card card : cards.getCards(game)) {
for (Card card : cardsToMove.getCards(game)) {
game.getPlayer(card.getOwnerId()).getHand().add(card);
}
break;
case GRAVEYARD:
for (Card card : chooseOrder(
"order to put in graveyard (last chosen will be on top)", cards, owner, game)) {
"order to put in graveyard (last chosen will be on top)", cardsToMove, owner, game)) {
game.getPlayer(card.getOwnerId()).getGraveyard().add(card);
}
break;
case LIBRARY:
if (info instanceof ZoneChangeInfo.Library && ((ZoneChangeInfo.Library) info).top) {
// on top
for (Card card : chooseOrder(
"order to put on top of library (last chosen will be topmost)", cards, owner, game)) {
"order to put on top of library (last chosen will be topmost)", cardsToMove, owner, game)) {
game.getPlayer(card.getOwnerId()).getLibrary().putOnTop(card, game);
}
} else { // buttom
} else {
// on bottom
for (Card card : chooseOrder(
"order to put on bottom of library (last chosen will be bottommost)", cards, owner, game)) {
"order to put on bottom of library (last chosen will be bottommost)", cardsToMove, owner, game)) {
game.getPlayer(card.getOwnerId()).getLibrary().putOnBottom(card, game);
}
}
break;
case EXILED:
for (Card card : cards.getCards(game)) {
for (Card card : cardsToMove.getCards(game)) {
if (info instanceof ZoneChangeInfo.Exile && ((ZoneChangeInfo.Exile) info).id != null) {
ZoneChangeInfo.Exile exileInfo = (ZoneChangeInfo.Exile) info;
game.getExile().createZone(exileInfo.id, exileInfo.name).add(card);
@ -147,13 +159,13 @@ public final class ZonesHandler {
break;
case COMMAND:
// There should never be more than one card here.
for (Card card : cards.getCards(game)) {
for (Card card : cardsToMove.getCards(game)) {
game.addCommander(new Commander(card));
}
break;
case STACK:
// There should never be more than one card here.
for (Card card : cards.getCards(game)) {
for (Card card : cardsToMove.getCards(game)) {
Spell spell;
if (info instanceof ZoneChangeInfo.Stack && ((ZoneChangeInfo.Stack) info).spell != null) {
spell = ((ZoneChangeInfo.Stack) info).spell;
@ -174,28 +186,39 @@ public final class ZonesHandler {
throw new UnsupportedOperationException("to Zone " + toZone.toString() + " not supported yet");
}
}
// update zone in main
game.setZone(event.getTargetId(), event.getToZone());
if (targetCard instanceof MeldCard && cards != null) {
// update zone in other parts (meld cards, mdf half cards)
if (cardsToUpdate != null) {
for (Card card : cardsToUpdate.getCards(game)) {
if (!card.getId().equals(event.getTargetId())) {
game.setZone(card.getId(), event.getToZone());
}
}
}
// reset meld status
if (targetCard instanceof MeldCard) {
if (event.getToZone() != Zone.BATTLEFIELD) {
((MeldCard) targetCard).setMelded(false, game);
}
for (Card card : cards.getCards(game)) {
game.setZone(card.getId(), event.getToZone());
}
}
}
public static Card getTargetCard(Game game, UUID targetId) {
if (game.getCard(targetId) != null) {
return game.getCard(targetId);
Card card = game.getCard(targetId);
if (card != null) {
return card;
}
if (game.getMeldCard(targetId) != null) {
return game.getMeldCard(targetId);
card = game.getMeldCard(targetId);
if (card != null) {
return card;
}
if (game.getPermanent(targetId) != null) {
return game.getPermanent(targetId);
}
return null;
return game.getPermanent(targetId);
}
private static boolean maybeRemoveFromSourceZone(ZoneChangeInfo info, Game game) {
@ -203,7 +226,7 @@ public final class ZonesHandler {
if (info instanceof ZoneChangeInfo.Unmelded) {
ZoneChangeInfo.Unmelded unmelded = (ZoneChangeInfo.Unmelded) info;
MeldCard meld = game.getMeldCard(info.event.getTargetId());
for (Iterator<ZoneChangeInfo> itr = unmelded.subInfo.iterator(); itr.hasNext();) {
for (Iterator<ZoneChangeInfo> itr = unmelded.subInfo.iterator(); itr.hasNext(); ) {
ZoneChangeInfo subInfo = itr.next();
if (!maybeRemoveFromSourceZone(subInfo, game)) {
itr.remove();
@ -232,10 +255,10 @@ public final class ZonesHandler {
if (info.faceDown) {
card.setFaceDown(true, game);
} else if (info.event.getToZone().equals(Zone.BATTLEFIELD)) {
if (!card.isPermanent()
if (!card.isPermanent()
&& (!card.isTransformable() || Boolean.FALSE.equals(game.getState().getValue(TransformAbility.VALUE_KEY_ENTER_TRANSFORMED + card.getId())))) {
// Non permanents (Instants, Sorceries, ... stay in the zone they are if an abilty/effect tries to move it to the battlefield
return false;
return false;
}
}
if (!game.replaceEvent(event)) {
@ -248,9 +271,10 @@ public final class ZonesHandler {
Permanent permanent;
if (card instanceof MeldCard) {
permanent = new PermanentMeld(card, event.getPlayerId(), game);
} else if (card instanceof ModalDoubleFacesCard) {
throw new IllegalStateException("Unexpected trying of move mdf card to battlefield instead half");
} else if (card instanceof Permanent) {
// This should never happen.
permanent = (Permanent) card;
throw new IllegalStateException("Unexpected trying of move permanent to battlefield instead card");
} else {
permanent = new PermanentCard(card, event.getPlayerId(), game);
}

View file

@ -91,6 +91,7 @@ public class GameEvent implements Serializable {
CLASH, CLASHED,
DAMAGE_PLAYER,
MILL_CARDS,
MILLED_CARD,
/* DAMAGED_PLAYER
targetId the id of the damaged player
sourceId sourceId of the ability which caused the damage
@ -249,13 +250,17 @@ public class GameEvent implements Serializable {
SHUFFLE_LIBRARY, LIBRARY_SHUFFLED,
ENCHANT_PLAYER, ENCHANTED_PLAYER,
CAN_TAKE_MULLIGAN,
FLIP_COIN, COIN_FLIPPED, SCRY, SURVEIL, SURVEILED, FATESEAL,
SCRY, SCRIED,
SURVEIL, SURVEILED,
FATESEALED,
FLIP_COIN, COIN_FLIPPED,
ROLL_DICE, DICE_ROLLED,
ROLL_PLANAR_DIE, PLANAR_DIE_ROLLED,
PLANESWALK, PLANESWALKED,
PAID_CUMULATIVE_UPKEEP,
DIDNT_PAY_CUMULATIVE_UPKEEP,
LIFE_PAID,
CASCADE_LAND,
//permanent events
ENTERS_THE_BATTLEFIELD_SELF, /* 616.1a If any of the replacement and/or prevention effects are self-replacement effects (see rule 614.15),
one of them must be chosen. If not, proceed to rule 616.1b. */

View file

@ -264,7 +264,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
// ability hints
List<String> abilityHints = new ArrayList<>();
if (HintUtils.ABILITY_HINTS_ENABLE) {
for (Ability ability : abilities) {
for (Ability ability : getAbilities(game)) {
for (Hint hint : ability.getHints()) {
String s = hint.getText(game, ability);
if (s != null && !s.isEmpty()) {
@ -341,7 +341,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
return rules;
} catch (Exception e) {
return rulesError;
return CardUtil.RULES_ERROR_INFO;
}
}

View file

@ -1,13 +1,11 @@
package mage.game.permanent.token;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.MageInt;
import mage.abilities.keyword.ChangelingAbility;
import mage.constants.CardType;
import mage.constants.SubType;
/**
*
* @author spjspj
*/
public final class CribSwapShapeshifterWhiteToken extends TokenImpl {
@ -19,6 +17,7 @@ public final class CribSwapShapeshifterWhiteToken extends TokenImpl {
subtype.add(SubType.SHAPESHIFTER);
power = new MageInt(1);
toughness = new MageInt(1);
setIsAllCreatureTypes(true);
addAbility(ChangelingAbility.getInstance());
}

View file

@ -0,0 +1,29 @@
package mage.game.permanent.token;
import mage.MageInt;
import mage.constants.CardType;
import mage.constants.SubType;
/**
* @author TheElk801
*/
public final class SalamnderWarriorToken extends TokenImpl {
public SalamnderWarriorToken() {
super("Salamander Warrior", "4/3 blue Salamander Warrior creature token");
cardType.add(CardType.CREATURE);
color.setBlue(true);
subtype.add(SubType.SALAMANDER);
subtype.add(SubType.WARRIOR);
power = new MageInt(4);
toughness = new MageInt(3);
}
public SalamnderWarriorToken(final SalamnderWarriorToken token) {
super(token);
}
public SalamnderWarriorToken copy() {
return new SalamnderWarriorToken(this);
}
}

View file

@ -27,6 +27,7 @@ public final class ShapeshifterToken extends TokenImpl {
subtype.add(SubType.SHAPESHIFTER);
power = new MageInt(2);
toughness = new MageInt(2);
setIsAllCreatureTypes(true);
addAbility(ChangelingAbility.getInstance());
}

View file

@ -178,6 +178,12 @@ public class Spell extends StackObjImpl implements Card {
+ " as Adventure spell of " + GameLog.getColoredObjectIdName(adventureCard);
}
if (card instanceof ModalDoubleFacesCardHalf) {
ModalDoubleFacesCard mdfCard = (ModalDoubleFacesCard) card.getMainCard();
return GameLog.replaceNameByColoredName(card, getSpellAbility().toString(), mdfCard)
+ " as mdf side of " + GameLog.getColoredObjectIdName(mdfCard);
}
return GameLog.replaceNameByColoredName(card, getSpellAbility().toString());
}
@ -247,7 +253,7 @@ public class Spell extends StackObjImpl implements Card {
// Otherwise effects like evolve trigger from creature comes into play event
card.getCardType().remove(CardType.CREATURE);
if (!card.getSubtype(game).contains(SubType.AURA)) {
card.getSubtype(game).add(SubType.AURA);
card.addSubType(game, SubType.AURA);
}
}
UUID permId = null;
@ -271,9 +277,7 @@ public class Spell extends StackObjImpl implements Card {
Permanent permanent = game.getPermanent(permId);
if (permanent instanceof PermanentCard) {
permanent.setSpellAbility(ability); // otherwise spell ability without bestow will be set
if (!card.getCardType().contains(CardType.CREATURE)) {
card.addCardType(CardType.CREATURE);
}
card.addCardType(CardType.CREATURE);
card.getSubtype(game).remove(SubType.AURA);
}
}
@ -714,11 +718,6 @@ public class Spell extends StackObjImpl implements Card {
return null;
}
@Override
public boolean isSplitCard() {
return false;
}
@Override
public boolean isTransformable() {
return false;

View file

@ -1309,7 +1309,14 @@ public abstract class PlayerImpl implements Player, Serializable {
card.getId(), card.getId(), playerId, activationStatus.getApprovingObject());
landEventAfter.setZone(cardZoneBefore);
game.fireEvent(landEventAfter);
game.fireInformEvent(getLogName() + " plays " + card.getLogName());
String playText = getLogName() + " plays " + card.getLogName();
if (card instanceof ModalDoubleFacesCardHalf) {
ModalDoubleFacesCard mdfCard = (ModalDoubleFacesCard) card.getMainCard();
playText = getLogName() + " plays " + GameLog.replaceNameByColoredName(card, card.getName(), mdfCard)
+ " as MDF side of " + GameLog.getColoredObjectIdName(mdfCard);
}
game.fireInformEvent(playText);
// game.removeBookmark(bookmark);
resetStoredBookmark(game); // prevent undo after playing a land
return true;
@ -1602,6 +1609,10 @@ public abstract class PlayerImpl implements Player, Serializable {
needId1 = object.getId();
needId2 = ((SplitCard) object).getLeftHalfCard().getId();
needId3 = ((SplitCard) object).getRightHalfCard().getId();
} else if (object instanceof ModalDoubleFacesCard) {
needId1 = object.getId();
needId2 = ((ModalDoubleFacesCard) object).getLeftHalfCard().getId();
needId3 = ((ModalDoubleFacesCard) object).getRightHalfCard().getId();
} else if (object instanceof AdventureCard) {
needId1 = object.getId();
needId2 = ((AdventureCard) object).getMainCard().getId();
@ -3402,10 +3413,16 @@ public abstract class PlayerImpl implements Player, Serializable {
// BASIC abilities
if (object instanceof SplitCard) {
SplitCard splitCard = (SplitCard) object;
getPlayableFromObjectSingle(game, fromZone, splitCard.getLeftHalfCard(), splitCard.getLeftHalfCard().getAbilities(game), availableMana, output);
getPlayableFromObjectSingle(game, fromZone, splitCard.getRightHalfCard(), splitCard.getRightHalfCard().getAbilities(game), availableMana, output);
getPlayableFromObjectSingle(game, fromZone, splitCard, splitCard.getSharedAbilities(game), availableMana, output);
SplitCard mainCard = (SplitCard) object;
getPlayableFromObjectSingle(game, fromZone, mainCard.getLeftHalfCard(), mainCard.getLeftHalfCard().getAbilities(game), availableMana, output);
getPlayableFromObjectSingle(game, fromZone, mainCard.getRightHalfCard(), mainCard.getRightHalfCard().getAbilities(game), availableMana, output);
getPlayableFromObjectSingle(game, fromZone, mainCard, mainCard.getSharedAbilities(game), availableMana, output);
}
if (object instanceof ModalDoubleFacesCard) {
ModalDoubleFacesCard mainCard = (ModalDoubleFacesCard) object;
getPlayableFromObjectSingle(game, fromZone, mainCard.getLeftHalfCard(), mainCard.getLeftHalfCard().getAbilities(game), availableMana, output);
getPlayableFromObjectSingle(game, fromZone, mainCard.getRightHalfCard(), mainCard.getRightHalfCard().getAbilities(game), availableMana, output);
getPlayableFromObjectSingle(game, fromZone, mainCard, mainCard.getSharedAbilities(game), availableMana, output);
} else if (object instanceof AdventureCard) {
// adventure must use different card characteristics for different spells (main or adventure)
AdventureCard adventureCard = (AdventureCard) object;
@ -4414,6 +4431,9 @@ public abstract class PlayerImpl implements Player, Serializable {
}
Cards cards = new CardsImpl(this.getLibrary().getTopCards(game, event.getAmount()));
this.moveCards(cards, Zone.GRAVEYARD, source, game);
for (Card card : cards.getCards(game)) {
game.fireEvent(GameEvent.getEvent(EventType.MILLED_CARD, card.getId(), source.getSourceId(), getId()));
}
return cards;
}
@ -4525,9 +4545,14 @@ public abstract class PlayerImpl implements Player, Serializable {
@Override
public boolean scry(int value, Ability source, Game game) {
game.informPlayers(getLogName() + " scries " + value);
GameEvent event = new GameEvent(EventType.SCRY, getId(), source == null
? null : source.getSourceId(), getId(), value, true);
if (game.replaceEvent(event)) {
return false;
}
game.informPlayers(getLogName() + " scries " + event.getAmount());
Cards cards = new CardsImpl();
cards.addAll(getLibrary().getTopCards(game, value));
cards.addAll(getLibrary().getTopCards(game, event.getAmount()));
if (!cards.isEmpty()) {
TargetCard target = new TargetCard(0, cards.size(), Zone.LIBRARY,
new FilterCard("card" + (cards.size() == 1 ? "" : "s")
@ -4537,8 +4562,8 @@ public abstract class PlayerImpl implements Player, Serializable {
cards.removeAll(target.getTargets());
putCardsOnTopOfLibrary(cards, game, source, true);
}
game.fireEvent(new GameEvent(GameEvent.EventType.SCRY, getId(), source == null
? null : source.getSourceId(), getId(), value, true));
game.fireEvent(new GameEvent(GameEvent.EventType.SCRIED, getId(), source == null
? null : source.getSourceId(), getId(), event.getAmount(), true));
return true;
}

View file

@ -1,8 +1,8 @@
package mage.target.common;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.filter.FilterPermanent;
import mage.filter.StaticFilters;
import mage.filter.common.FilterCreaturePermanent;
/**
* @author North
@ -17,11 +17,11 @@ public class TargetCreaturePermanentAmount extends TargetPermanentAmount {
this(amount, StaticFilters.FILTER_PERMANENT_CREATURE);
}
public TargetCreaturePermanentAmount(int amount, FilterCreaturePermanent filter) {
public TargetCreaturePermanentAmount(int amount, FilterPermanent filter) {
super(amount, filter);
}
public TargetCreaturePermanentAmount(DynamicValue amount, FilterCreaturePermanent filter) {
public TargetCreaturePermanentAmount(DynamicValue amount, FilterPermanent filter) {
super(amount, filter);
}

View file

@ -37,7 +37,7 @@ public class TargetCreaturePermanentWithDifferentTypes extends TargetCreaturePer
Permanent selectedCreature = game.getPermanent(targetId);
if (selectedCreature != null
&& !creature.getId().equals(selectedCreature.getId())) {
if (creature.shareSubtypes(selectedCreature, game)) {
if (creature.shareCreatureTypes(selectedCreature, game)) {
return false;
}
}

View file

@ -1,11 +1,6 @@
package mage.util;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;
import com.google.common.collect.ImmutableList;
import mage.MageObject;
import mage.Mana;
import mage.abilities.Abilities;
@ -15,8 +10,12 @@ import mage.abilities.SpellAbility;
import mage.abilities.costs.VariableCost;
import mage.abilities.costs.mana.*;
import mage.abilities.effects.ContinuousEffect;
import mage.abilities.hint.Hint;
import mage.abilities.hint.HintUtils;
import mage.cards.Card;
import mage.cards.MeldCard;
import mage.cards.ModalDoubleFacesCard;
import mage.cards.SplitCard;
import mage.constants.*;
import mage.filter.Filter;
import mage.filter.predicate.mageobject.NamePredicate;
@ -30,19 +29,31 @@ import mage.game.stack.Spell;
import mage.players.Player;
import mage.target.Target;
import mage.util.functions.CopyTokenFunction;
import org.apache.log4j.Logger;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;
/**
* @author nantuko
*/
public final class CardUtil {
private static final Logger logger = Logger.getLogger(CardUtil.class);
public static final List<String> RULES_ERROR_INFO = ImmutableList.of("Exception occurred in rules generation");
private static final String SOURCE_EXILE_ZONE_TEXT = "SourceExileZone";
static final String[] numberStrings = {"zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine",
"ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen", "twenty"};
"ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen", "twenty"};
static final String[] ordinalStrings = {"first", "second", "third", "fourth", "fifth", "sixth", "seventh", "eightth", "ninth",
"tenth", "eleventh", "twelfth", "thirteenth", "fourteenth", "fifteenth", "sixteenth", "seventeenth", "eighteenth", "nineteenth", "twentieth"};
"tenth", "eleventh", "twelfth", "thirteenth", "fourteenth", "fifteenth", "sixteenth", "seventeenth", "eighteenth", "nineteenth", "twentieth"};
public static final SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
@ -877,4 +888,71 @@ public final class CardUtil {
}
}
}
/**
* Return card name for same name searching
*
* @param card
* @return
*/
public static String getCardNameForSameNameSearch(Card card) {
// it's ok to return one name only cause NamePredicate can find same card by first name
if (card instanceof SplitCard) {
return ((SplitCard) card).getLeftHalfCard().getName();
} else if (card instanceof ModalDoubleFacesCard) {
return ((ModalDoubleFacesCard) card).getLeftHalfCard().getName();
} else {
return card.getName();
}
}
public static List<String> getCardRulesWithAdditionalInfo(UUID cardId, String cardName,
Abilities<Ability> rulesSource, Abilities<Ability> hintAbilities) {
return getCardRulesWithAdditionalInfo(null, cardId, cardName, rulesSource, hintAbilities);
}
/**
* Prepare rules list from abilities
*
* @param rulesSource abilities list to show as rules
* @param hintsSource abilities list to show as card hints only (you can add additional hints here; exameple: from second or transformed side)
*/
public static List<String> getCardRulesWithAdditionalInfo(Game game, UUID cardId, String cardName,
Abilities<Ability> rulesSource, Abilities<Ability> hintsSource) {
try {
List<String> rules = rulesSource.getRules(cardName);
if (game != null) {
// debug state
for (String data : game.getState().getCardState(cardId).getInfo().values()) {
rules.add(data);
}
// ability hints
List<String> abilityHints = new ArrayList<>();
if (HintUtils.ABILITY_HINTS_ENABLE) {
for (Ability ability : hintsSource) {
for (Hint hint : ability.getHints()) {
String s = hint.getText(game, ability);
if (s != null && !s.isEmpty()) {
abilityHints.add(s);
}
}
}
}
// restrict hints only for permanents, not cards
// total hints
if (!abilityHints.isEmpty()) {
rules.add(HintUtils.HINT_START_MARK);
HintUtils.appendHints(rules, abilityHints);
}
}
return rules;
} catch (Exception e) {
logger.error("Exception in rules generation for card: " + cardName, e);
}
return RULES_ERROR_INFO;
}
}

View file

@ -28,7 +28,7 @@ public class AddSubtypeApplier extends ApplyToPermanent {
@Override
public boolean apply(Game game, Permanent permanent, Ability source, UUID copyToObjectId) {
if (!permanent.hasSubtype(subtype, game)) {
permanent.getSubtype(game).add(subtype);
permanent.addSubType(game, subtype);
}
return true;
}
@ -36,7 +36,7 @@ public class AddSubtypeApplier extends ApplyToPermanent {
@Override
public boolean apply(Game game, MageObject mageObject, Ability source, UUID copyToObjectId) {
if (!mageObject.hasSubtype(subtype, game)) {
mageObject.getSubtype(game).add(subtype);
mageObject.addSubType(game, subtype);
}
return true;
}

View file

@ -1,7 +1,5 @@
package mage.watchers.common;
import mage.abilities.keyword.ChangelingAbility;
import mage.constants.SubType;
import mage.constants.WatcherScope;
import mage.game.Game;
@ -30,22 +28,24 @@ public class ProwlWatcher extends Watcher {
@Override
public void watch(GameEvent event, Game game) {
if (event.getType() == EventType.DAMAGED_PLAYER) {
DamagedPlayerEvent dEvent = (DamagedPlayerEvent) event;
if (dEvent.isCombatDamage()) {
Permanent creature = game.getPermanent(dEvent.getSourceId());
if (creature != null && !allSubtypes.contains(creature.getControllerId())) {
if (creature.getAbilities().containsKey(ChangelingAbility.getInstance().getId()) || creature.isAllCreatureTypes()) {
allSubtypes.add(creature.getControllerId());
} else {
Set<SubType> subtypes = damagingSubtypes.getOrDefault(creature.getControllerId(), new LinkedHashSet<>());
subtypes.addAll(creature.getSubtype(game));
damagingSubtypes.put(creature.getControllerId(), subtypes);
}
}
}
if (event.getType() != EventType.DAMAGED_PLAYER) {
return;
}
DamagedPlayerEvent dEvent = (DamagedPlayerEvent) event;
if (!dEvent.isCombatDamage()) {
return;
}
Permanent creature = game.getPermanent(dEvent.getSourceId());
if (creature == null || allSubtypes.contains(creature.getControllerId())) {
return;
}
if (creature.isAllCreatureTypes()) {
allSubtypes.add(creature.getControllerId());
return;
}
damagingSubtypes
.computeIfAbsent(creature.getControllerId(), m -> new LinkedHashSet<>())
.addAll(creature.getSubtype(game));
}
@Override