Merge branch 'master' into add-minimum-rating-option

This commit is contained in:
LevelX2 2018-11-26 17:18:35 +01:00 committed by GitHub
commit e7d129a074
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
568 changed files with 21414 additions and 6453 deletions

View file

@ -479,16 +479,7 @@ public interface Ability extends Controllable, Serializable {
boolean activateAlternateOrAdditionalCosts(MageObject sourceObject, boolean noMana, Player controller, Game game);
/**
* Sets the object that actually existed while a ability triggerd or an
* ability was activated.
*
* @param mageObject
* @param game
*/
void setSourceObject(MageObject mageObject, Game game);
/**
* Returns the object that actually existed while a ability triggerd or an
* Returns the object that actually existed while a ability triggered or an
* ability was activated. If not set yet, the current object will be
* retrieved from the game.
*
@ -497,6 +488,8 @@ public interface Ability extends Controllable, Serializable {
*/
MageObject getSourceObject(Game game);
void setSourceObjectZoneChangeCounter(int zoneChangeCounter);
int getSourceObjectZoneChangeCounter();
/**
@ -520,6 +513,8 @@ public interface Ability extends Controllable, Serializable {
*/
Permanent getSourcePermanentIfItStillExists(Game game);
Permanent getSourcePermanentOrLKI(Game game);
String getTargetDescription(Targets targets, Game game);
void setCanFizzle(boolean canFizzle);

View file

@ -5,7 +5,6 @@ import java.util.Iterator;
import java.util.List;
import java.util.UUID;
import mage.MageObject;
import mage.MageObjectReference;
import mage.Mana;
import mage.abilities.costs.*;
import mage.abilities.costs.common.PayLifeCost;
@ -15,8 +14,8 @@ import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.Effect;
import mage.abilities.effects.Effects;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.mana.DynamicManaEffect;
import mage.abilities.effects.common.ManaEffect;
import mage.abilities.effects.mana.DynamicManaEffect;
import mage.abilities.mana.ActivatedManaAbilityImpl;
import mage.cards.Card;
import mage.cards.SplitCard;
@ -67,7 +66,6 @@ public abstract class AbilityImpl implements Ability {
protected boolean costModificationActive = true;
protected boolean activated = false;
protected boolean worksFaceDown = false;
protected MageObject sourceObject;
protected int sourceObjectZoneChangeCounter;
protected List<Watcher> watchers = new ArrayList<>();
protected List<Ability> subAbilities = null;
@ -116,7 +114,6 @@ public abstract class AbilityImpl implements Ability {
this.costModificationActive = ability.costModificationActive;
this.worksFaceDown = ability.worksFaceDown;
this.abilityWord = ability.abilityWord;
this.sourceObject = ability.sourceObject;
this.sourceObjectZoneChangeCounter = ability.sourceObjectZoneChangeCounter;
this.canFizzle = ability.canFizzle;
this.targetAdjuster = ability.targetAdjuster;
@ -131,8 +128,6 @@ public abstract class AbilityImpl implements Ability {
public void newId() {
if (!(this instanceof MageSingleton)) {
this.id = UUID.randomUUID();
// this.sourceObject = null;
// this.sourceObjectZoneChangeCounter = -1;
}
getEffects().newId();
}
@ -226,8 +221,10 @@ public abstract class AbilityImpl implements Ability {
return false;
}
getSourceObject(game);
MageObject sourceObject = getSourceObject(game);
if (getSourceObjectZoneChangeCounter() == 0) {
setSourceObjectZoneChangeCounter(game.getState().getZoneChangeCounter(getSourceId()));
}
if (controller.isTestMode()) {
if (!controller.addTargets(this, game)) {
return false;
@ -1160,58 +1157,44 @@ public abstract class AbilityImpl implements Ability {
@Override
public MageObject getSourceObject(Game game) {
if (sourceObject == null) {
setSourceObject(null, game);
if (sourceObject == null) {
logger.warn("Source object could not be retrieved: " + this.getRule());
}
}
return sourceObject;
return game.getObject(getSourceId());
}
@Override
public MageObject getSourceObjectIfItStillExists(Game game) {
MageObject currentObject = game.getObject(getSourceId());
if (currentObject != null) {
if (sourceObject == null) {
setSourceObject(currentObject, game);
}
MageObjectReference mor = new MageObjectReference(currentObject, game);
if (mor.getZoneChangeCounter() == getSourceObjectZoneChangeCounter()) {
// source object has meanwhile not changed zone
return currentObject;
}
if (getSourceObjectZoneChangeCounter() == 0
|| getSourceObjectZoneChangeCounter() == game.getState().getZoneChangeCounter(getSourceId())) {
return game.getObject(getSourceId());
}
return null;
}
@Override
public Permanent getSourcePermanentIfItStillExists(Game game) {
if (sourceObject == null || !sourceObject.getId().equals(getSourceId())) {
setSourceObject(game.getObject(getSourceId()), game);
}
if (sourceObject instanceof Permanent) {
if (game.getState().getZoneChangeCounter(getSourceId()) == getSourceObjectZoneChangeCounter()) {
return (Permanent) sourceObject;
}
MageObject mageObject = getSourceObjectIfItStillExists(game);
if (mageObject instanceof Permanent) {
return (Permanent) mageObject;
}
return null;
}
@Override
public int getSourceObjectZoneChangeCounter() {
return sourceObjectZoneChangeCounter;
public Permanent getSourcePermanentOrLKI(Game game) {
if (getSourceObjectZoneChangeCounter() == 0
|| getSourceObjectZoneChangeCounter() == game.getState().getZoneChangeCounter(getSourceId())) {
return game.getPermanent(getSourceId());
}
return (Permanent) game.getLastKnownInformation(getSourceId(), Zone.BATTLEFIELD, getSourceObjectZoneChangeCounter());
}
@Override
public void setSourceObject(MageObject sourceObject, Game game) {
if (sourceObject == null) {
this.sourceObject = game.getObject(sourceId);
this.sourceObjectZoneChangeCounter = game.getState().getZoneChangeCounter(sourceId);
} else {
this.sourceObject = sourceObject;
this.sourceObjectZoneChangeCounter = this.sourceObject.getZoneChangeCounter(game);
}
public void setSourceObjectZoneChangeCounter(int sourceObjectZoneChangeCounter) {
this.sourceObjectZoneChangeCounter = sourceObjectZoneChangeCounter;
}
@Override
public int getSourceObjectZoneChangeCounter() {
return sourceObjectZoneChangeCounter;
}
@Override

View file

@ -219,9 +219,9 @@ public abstract class ActivatedAbilityImpl extends AbilityImpl implements Activa
} else {
MageObject mageObject = game.getObject(this.sourceId);
if (mageObject instanceof Emblem) {
return ((Emblem) mageObject).getControllerId().equals(playerId);
return ((Emblem) mageObject).isControlledBy(playerId);
} else if (mageObject instanceof Plane) {
return ((Plane) mageObject).getControllerId().equals(playerId);
return ((Plane) mageObject).isControlledBy(playerId);
} else if (game.getState().getZone(this.sourceId) != Zone.BATTLEFIELD) {
return ((Card) mageObject).isOwnedBy(playerId);
}

View file

@ -1,4 +1,3 @@
package mage.abilities;
import java.util.UUID;
@ -31,7 +30,10 @@ public class PlayLandAbility extends ActivatedAbilityImpl {
return ActivationStatus.getFalse();
}
//20091005 - 114.2a
return new ActivationStatus(game.isActivePlayer(playerId) && game.getPlayer(playerId).canPlayLand() && game.canPlaySorcery(playerId), permittingObject);
return new ActivationStatus(game.isActivePlayer(playerId)
&& game.getPlayer(playerId).canPlayLand()
&& game.canPlaySorcery(playerId),
permittingObject);
}
@Override

View file

@ -1,4 +1,3 @@
package mage.abilities;
import java.util.Locale;
@ -45,9 +44,6 @@ public abstract class TriggeredAbilityImpl extends AbilityImpl implements Trigge
@Override
public void trigger(Game game, UUID controllerId) {
//20091005 - 603.4
if (!(this instanceof DelayedTriggeredAbility)) {
setSourceObject(null, game);
}
if (checkInterveningIfClause(game)) {
game.addTriggeredAbility(this);
}

View file

@ -0,0 +1,50 @@
/*
* 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.common;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.Effect;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.GameEvent.EventType;
import mage.game.permanent.Permanent;
/**
*
* @author jeffwadsworth
*/
public class ControllerPlaysLandTriggeredAbility extends TriggeredAbilityImpl {
public ControllerPlaysLandTriggeredAbility(Zone zone, Effect effect, Boolean optional) {
super(zone, effect, optional);
}
public ControllerPlaysLandTriggeredAbility(ControllerPlaysLandTriggeredAbility ability) {
super(ability);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == EventType.LAND_PLAYED;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
Permanent land = game.getPermanent(event.getTargetId());
return land.getControllerId().equals(controllerId);
}
@Override
public ControllerPlaysLandTriggeredAbility copy() {
return new ControllerPlaysLandTriggeredAbility(this);
}
@Override
public String getRule() {
return "Whenever you play a land, ";
}
}

View file

@ -1,123 +0,0 @@
package mage.abilities.common;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.Effect;
import mage.constants.Zone;
import mage.filter.common.FilterCreaturePermanent;
import mage.game.Game;
import mage.game.events.EntersTheBattlefieldEvent;
import mage.game.events.GameEvent;
import mage.game.events.GameEvent.EventType;
import mage.game.permanent.Permanent;
import mage.target.Target;
import mage.target.TargetPlayer;
import mage.target.common.TargetCreaturePermanent;
/**
*
* @author North
*/
public class CreatureEntersBattlefieldTriggeredAbility extends TriggeredAbilityImpl {
private boolean opponentController;
protected FilterCreaturePermanent filter = new FilterCreaturePermanent();
/**
* optional = false<br>
* opponentController = false
*
* @param effect
*/
public CreatureEntersBattlefieldTriggeredAbility(Effect effect) {
this(effect, false, false);
}
/**
* opponentController = false
*
* @param effect
* @param optional
*/
public CreatureEntersBattlefieldTriggeredAbility(Effect effect, boolean optional) {
this(effect, optional, false);
}
/**
*
* @param effect
* @param optional
* @param opponentController
*/
public CreatureEntersBattlefieldTriggeredAbility(Effect effect, boolean optional, boolean opponentController) {
this(Zone.BATTLEFIELD, effect, optional, opponentController);
}
/**
* @param zone
* @param effect
* @param optional
* @param opponentController
*/
public CreatureEntersBattlefieldTriggeredAbility(Zone zone, Effect effect, boolean optional, boolean opponentController) {
this(zone, effect, null, optional, opponentController);
}
/**
* @param zone
* @param effect
* @param filter filter the triggering creatures
* @param optional
* @param opponentController
*/
public CreatureEntersBattlefieldTriggeredAbility(Zone zone, Effect effect, FilterCreaturePermanent filter, boolean optional, boolean opponentController) {
super(zone, effect, optional);
this.opponentController = opponentController;
if (filter != null) {
this.filter = filter;
}
}
public CreatureEntersBattlefieldTriggeredAbility(CreatureEntersBattlefieldTriggeredAbility ability) {
super(ability);
this.opponentController = ability.opponentController;
this.filter = ability.filter;
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == EventType.ENTERS_THE_BATTLEFIELD;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
Permanent permanent = ((EntersTheBattlefieldEvent) event).getTarget();
if (filter.match(permanent, sourceId, controllerId, game)
&& (permanent.isControlledBy(this.controllerId) ^ opponentController)) {
if (!this.getTargets().isEmpty()) {
Target target = this.getTargets().get(0);
if (target instanceof TargetPlayer) {
target.add(permanent.getControllerId(), game);
}
if (target instanceof TargetCreaturePermanent) {
target.add(event.getTargetId(), game);
}
}
return true;
}
return false;
}
@Override
public String getRule() {
return "Whenever a " + filter.getMessage() + " enters the battlefield under "
+ (opponentController ? "an opponent's control, " : "your control, ")
+ super.getRule();
}
@Override
public CreatureEntersBattlefieldTriggeredAbility copy() {
return new CreatureEntersBattlefieldTriggeredAbility(this);
}
}

View file

@ -13,7 +13,6 @@ import mage.game.permanent.Permanent;
import mage.target.targetpointer.FixedTarget;
/**
*
* @author LevelX2
*/
public class DealsDamageToAPlayerAllTriggeredAbility extends TriggeredAbilityImpl {
@ -28,7 +27,11 @@ public class DealsDamageToAPlayerAllTriggeredAbility extends TriggeredAbilityImp
}
public DealsDamageToAPlayerAllTriggeredAbility(Effect effect, FilterPermanent filter, boolean optional, SetTargetPointer setTargetPointer, boolean onlyCombat, boolean affectsDefendingPlayer) {
super(Zone.BATTLEFIELD, effect, optional);
this(Zone.BATTLEFIELD, effect, filter, optional, setTargetPointer, onlyCombat, affectsDefendingPlayer);
}
public DealsDamageToAPlayerAllTriggeredAbility(Zone zone, Effect effect, FilterPermanent filter, boolean optional, SetTargetPointer setTargetPointer, boolean onlyCombat, boolean affectsDefendingPlayer) {
super(zone, effect, optional);
this.setTargetPointer = setTargetPointer;
this.filter = filter;
this.onlyCombat = onlyCombat;
@ -57,27 +60,25 @@ public class DealsDamageToAPlayerAllTriggeredAbility extends TriggeredAbilityImp
public boolean checkTrigger(GameEvent event, Game game) {
if (!onlyCombat || ((DamagedPlayerEvent) event).isCombatDamage()) {
Permanent permanent = game.getPermanent(event.getSourceId());
if (permanent != null) {
if (filter.match(permanent, getSourceId(), getControllerId(), game)) {
for (Effect effect : this.getEffects()) {
effect.setValue("damage", event.getAmount());
effect.setValue("sourceId", event.getSourceId());
if (affectsDefendingPlayer) {
effect.setTargetPointer(new FixedTarget(event.getTargetId()));
continue;
}
switch (setTargetPointer) {
case PLAYER:
effect.setTargetPointer(new FixedTarget(permanent.getControllerId()));
break;
case PERMANENT:
effect.setTargetPointer(new FixedTarget(permanent.getId(), permanent.getZoneChangeCounter(game)));
break;
}
if (permanent != null && filter.match(permanent, getSourceId(), getControllerId(), game)) {
for (Effect effect : this.getEffects()) {
effect.setValue("damage", event.getAmount());
effect.setValue("sourceId", event.getSourceId());
if (affectsDefendingPlayer) {
effect.setTargetPointer(new FixedTarget(event.getTargetId()));
continue;
}
return true;
switch (setTargetPointer) {
case PLAYER:
effect.setTargetPointer(new FixedTarget(permanent.getControllerId()));
break;
case PERMANENT:
effect.setTargetPointer(new FixedTarget(permanent.getId(), permanent.getZoneChangeCounter(game)));
break;
}
}
return true;
}
}
return false;

View file

@ -0,0 +1,49 @@
/*
* 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.common;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.Effect;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
/**
*
* @author jeffwadsworth
*/
public class OpponentPlaysLandTriggeredAbility extends TriggeredAbilityImpl {
public OpponentPlaysLandTriggeredAbility(Zone zone, Effect effect, Boolean optional) {
super(zone, effect, optional);
}
public OpponentPlaysLandTriggeredAbility(OpponentPlaysLandTriggeredAbility ability) {
super(ability);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.LAND_PLAYED;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
Permanent land = game.getPermanent(event.getTargetId());
return game.getOpponents(controllerId).contains(land.getControllerId());
}
@Override
public OpponentPlaysLandTriggeredAbility copy() {
return new OpponentPlaysLandTriggeredAbility(this);
}
@Override
public String getRule() {
return "Whenever an opponent plays a land, ";
}
}

View file

@ -1,4 +1,3 @@
package mage.abilities.common;
import mage.abilities.Ability;
@ -138,7 +137,8 @@ class ChapterTriggeredAbility extends TriggeredAbilityImpl {
if (event.getTargetId().equals(getSourceId()) && event.getData().equals(CounterType.LORE.getName())) {
int amountAdded = event.getAmount();
int loreCounters = amountAdded;
Permanent sourceSaga = game.getPermanentOrLKIBattlefield(getSourceId());
//Permanent sourceSaga = game.getPermanentOrLKIBattlefield(getSourceId()); BUG #5393
Permanent sourceSaga = game.getPermanent(getSourceId());
if (sourceSaga == null) {
sourceSaga = game.getPermanentEntering(getSourceId());
}

View file

@ -1,4 +1,3 @@
package mage.abilities.common.delayed;
import java.util.LinkedHashSet;

View file

@ -0,0 +1,44 @@
package mage.abilities.dynamicvalue.common;
import mage.abilities.Ability;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.effects.Effect;
import mage.game.Game;
import mage.watchers.common.PlayersAttackedThisTurnWatcher;
import java.util.UUID;
/**
* @author JayDi85
*/
public class AttackedThisTurnOpponentsCount implements DynamicValue {
@Override
public int calculate(Game game, Ability sourceAbility, Effect effect) {
return this.calculate(game, sourceAbility.getControllerId());
}
public int calculate(Game game, UUID controllerId) {
PlayersAttackedThisTurnWatcher watcher = (PlayersAttackedThisTurnWatcher) game.getState().getWatchers().get(PlayersAttackedThisTurnWatcher.class.getSimpleName());
if (watcher != null) {
return watcher.getAttackedOpponentsCount(controllerId);
}
return 0;
}
@Override
public AttackedThisTurnOpponentsCount copy() {
return new AttackedThisTurnOpponentsCount();
}
@Override
public String toString() {
return "1";
}
@Override
public String getMessage() {
return "the number of opponents you attacked this turn";
}
}

View file

@ -0,0 +1,32 @@
package mage.abilities.dynamicvalue.common;
import mage.abilities.Ability;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.effects.Effect;
import mage.game.Game;
/**
* @author JayDi85
*/
public class OpponentsCount implements DynamicValue {
@Override
public int calculate(Game game, Ability sourceAbility, Effect effect) {
return game.getOpponents(sourceAbility.getControllerId()).size();
}
@Override
public OpponentsCount copy() {
return new OpponentsCount();
}
@Override
public String getMessage() {
return "number of opponents you have";
}
@Override
public String toString() {
return "1";
}
}

View file

@ -13,6 +13,7 @@ import mage.abilities.keyword.SpliceOntoArcaneAbility;
import mage.cards.Card;
import mage.cards.Cards;
import mage.cards.CardsImpl;
import mage.cards.SplitCardHalf;
import mage.constants.*;
import mage.filter.FilterCard;
import mage.filter.predicate.Predicate;
@ -488,15 +489,23 @@ public class ContinuousEffects implements Serializable {
*/
public MageObjectReference asThough(UUID objectId, AsThoughEffectType type, Ability affectedAbility, UUID controllerId, Game game) {
List<AsThoughEffect> asThoughEffectsList = getApplicableAsThoughEffects(type, game);
for (AsThoughEffect effect : asThoughEffectsList) {
Set<Ability> abilities = asThoughEffectsMap.get(type).getAbility(effect.getId());
for (Ability ability : abilities) {
if (affectedAbility == null) {
if (effect.applies(objectId, ability, controllerId, game)) {
if (!asThoughEffectsList.isEmpty()) {
UUID idToCheck;
if (affectedAbility != null && affectedAbility.getSourceObject(game) instanceof SplitCardHalf) {
idToCheck = ((SplitCardHalf) affectedAbility.getSourceObject(game)).getParentCard().getId();
} else {
idToCheck = objectId;
}
for (AsThoughEffect effect : asThoughEffectsList) {
Set<Ability> abilities = asThoughEffectsMap.get(type).getAbility(effect.getId());
for (Ability ability : abilities) {
if (affectedAbility == null) {
if (effect.applies(idToCheck, ability, controllerId, game)) {
return new MageObjectReference(ability.getSourceObject(game), game);
}
} else if (effect.applies(idToCheck, affectedAbility, ability, game, controllerId)) {
return new MageObjectReference(ability.getSourceObject(game), game);
}
} else if (effect.applies(objectId, affectedAbility, ability, game, controllerId)) {
return new MageObjectReference(ability.getSourceObject(game), game);
}
}
}

View file

@ -1,4 +1,3 @@
package mage.abilities.effects;
import mage.MageObject;
@ -47,6 +46,7 @@ public abstract class RedirectionEffect extends ReplacementEffectImpl {
this.redirectTarget = effect.redirectTarget;
this.amountToRedirect = effect.amountToRedirect;
this.usageType = effect.usageType;
this.applyEffectsCounter = effect.applyEffectsCounter;
}
@Override
@ -80,6 +80,7 @@ public abstract class RedirectionEffect extends ReplacementEffectImpl {
if (applyEffectsCounter > 0) {
if (applyEffectsCounter < game.getState().getApplyEffectsCounter()) {
this.discard();
return false;
}
} else {
applyEffectsCounter = game.getState().getApplyEffectsCounter();

View file

@ -1,4 +1,3 @@
package mage.abilities.effects.common;
import mage.abilities.Ability;
@ -39,7 +38,9 @@ public class AttachEffect extends OneShotEffect {
Permanent sourcePermanent = game.getPermanent(source.getSourceId());
if (sourcePermanent != null) {
int zcc = game.getState().getZoneChangeCounter(sourcePermanent.getId());
if (zcc == source.getSourceObjectZoneChangeCounter() || zcc == source.getSourceObjectZoneChangeCounter() + 1) {
if (zcc == source.getSourceObjectZoneChangeCounter()
|| zcc == source.getSourceObjectZoneChangeCounter() + 1
|| zcc == source.getSourceObjectZoneChangeCounter() + 2) {
Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source));
if (permanent != null) {
return permanent.addAttachment(source.getSourceId(), game);

View file

@ -1,12 +1,12 @@
package mage.abilities.effects.common;
import java.util.UUID;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.SpellAbility;
import mage.abilities.effects.Effect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.keyword.EnchantAbility;
import mage.constants.Outcome;
import mage.constants.SubType;
import mage.filter.FilterPermanent;
@ -20,8 +20,9 @@ import mage.target.TargetPermanent;
import mage.util.functions.ApplyToPermanent;
import mage.util.functions.EmptyApplyToPermanent;
import java.util.UUID;
/**
*
* @author BetaSteward_at_googlemail.com
*/
public class CopyPermanentEffect extends OneShotEffect {
@ -59,7 +60,9 @@ public class CopyPermanentEffect extends OneShotEffect {
super(effect);
this.filter = effect.filter.copy();
this.applier = effect.applier;
this.bluePrintPermanent = effect.bluePrintPermanent;
if (effect.bluePrintPermanent != null) {
this.bluePrintPermanent = effect.bluePrintPermanent.copy();
}
this.useTargetOfAbility = effect.useTargetOfAbility;
}
@ -84,42 +87,70 @@ public class CopyPermanentEffect extends OneShotEffect {
}
if (copyFromPermanent != null) {
bluePrintPermanent = game.copyPermanent(copyFromPermanent, sourcePermanent.getId(), source, applier);
//if object is a copy of an aura, it needs to attach
if (bluePrintPermanent.hasSubtype(SubType.AURA, game)){
if (bluePrintPermanent == null) {
return false;
}
// if object is a copy of an aura, it needs to attach again for new target
if (bluePrintPermanent.hasSubtype(SubType.AURA, game)) {
//copied from mage.cards.c.CopyEnchantment.java
Target target = bluePrintPermanent.getSpellAbility().getTargets().get(0);
// permanent can be attached (Estrid's Mask) or enchant (Utopia Sprawl)
// TODO: fix Animate Dead -- it's can't be copied (can't retarget)
Outcome auraOutcome = Outcome.BoostCreature;
Target auraTarget = null;
// attach - search effect in spell ability (example: cast Utopia Sprawl, cast Estrid's Invocation on it)
for (Ability ability : bluePrintPermanent.getAbilities()) {
if (ability instanceof SpellAbility) {
for (Effect effect : ability.getEffects()) {
if (effect instanceof AttachEffect) {
auraOutcome = effect.getOutcome();
if (bluePrintPermanent.getSpellAbility().getTargets().size() > 0) {
auraTarget = bluePrintPermanent.getSpellAbility().getTargets().get(0);
auraOutcome = effect.getOutcome();
}
}
}
}
}
/*if this is a copy of a copy, the copy's target has been
*copied and needs to be cleared
*/
{
UUID targetId = target.getFirstTarget();
if(targetId != null)
target.remove(targetId);
// enchant - search in all abilities (example: cast Estrid's Invocation on enchanted creature by Estrid, the Masked second ability, cast Estrid's Invocation on it)
if (auraTarget == null) {
for (Ability ability : bluePrintPermanent.getAbilities()) {
if (ability instanceof EnchantAbility) {
if (ability.getTargets().size() > 0) { // Animate Dead don't have targets
auraTarget = ability.getTargets().get(0);
for (Effect effect : ability.getEffects()) {
// first outcome
auraOutcome = effect.getOutcome();
}
}
}
}
}
target.setNotTarget(true);
if (controller.choose(auraOutcome, target, source.getSourceId(), game)) {
UUID targetId = target.getFirstTarget();
Permanent targetPermanent = game.getPermanent(targetId);
Player targetPlayer = game.getPlayer(targetId);
if (targetPermanent != null) {
targetPermanent.addAttachment(sourcePermanent.getId(), game);
} else if (targetPlayer != null) {
targetPlayer.addAttachment(sourcePermanent.getId(), game);
} else {
return false;
/* if this is a copy of a copy, the copy's target has been
* copied and needs to be cleared
*/
if (auraTarget != null) {
// clear selected target
if (auraTarget.getFirstTarget() != null) {
auraTarget.remove(auraTarget.getFirstTarget());
}
// select new target
auraTarget.setNotTarget(true);
if (controller.choose(auraOutcome, auraTarget, source.getSourceId(), game)) {
UUID targetId = auraTarget.getFirstTarget();
Permanent targetPermanent = game.getPermanent(targetId);
Player targetPlayer = game.getPlayer(targetId);
if (targetPermanent != null) {
targetPermanent.addAttachment(sourcePermanent.getId(), game);
} else if (targetPlayer != null) {
targetPlayer.addAttachment(sourcePermanent.getId(), game);
} else {
return false;
}
}
}
}

View file

@ -1,4 +1,3 @@
package mage.abilities.effects.common;
import java.util.UUID;
@ -6,6 +5,8 @@ import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.Mode;
import mage.abilities.costs.Cost;
import mage.abilities.costs.mana.GenericManaCost;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.Effect;
import mage.abilities.effects.Effects;
@ -22,15 +23,22 @@ import mage.util.CardUtil;
public class DoUnlessAnyPlayerPaysEffect extends OneShotEffect {
protected Effects executingEffects = new Effects();
private final Cost cost;
protected Cost cost;
private String chooseUseText;
protected DynamicValue genericMana;
public DoUnlessAnyPlayerPaysEffect(Effect effect, DynamicValue genericMana) {
super(Outcome.Detriment);
this.genericMana = genericMana;
this.executingEffects.add(effect);
}
public DoUnlessAnyPlayerPaysEffect(Effect effect, Cost cost) {
this(effect, cost, null);
}
public DoUnlessAnyPlayerPaysEffect(Effect effect, Cost cost, String chooseUseText) {
super(Outcome.Benefit);
super(Outcome.Neutral);
this.executingEffects.add(effect);
this.cost = cost;
this.chooseUseText = chooseUseText;
@ -38,8 +46,13 @@ public class DoUnlessAnyPlayerPaysEffect extends OneShotEffect {
public DoUnlessAnyPlayerPaysEffect(final DoUnlessAnyPlayerPaysEffect effect) {
super(effect);
if (effect.cost != null) {
this.cost = effect.cost.copy();
}
if (effect.genericMana != null) {
this.genericMana = effect.genericMana.copy();
}
this.executingEffects = effect.executingEffects.copy();
this.cost = effect.cost.copy();
this.chooseUseText = effect.chooseUseText;
}
@ -51,11 +64,18 @@ public class DoUnlessAnyPlayerPaysEffect extends OneShotEffect {
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
MageObject sourceObject = game.getObject(source.getSourceId());
if (controller != null && sourceObject != null) {
Cost costToPay;
if (controller != null
&& sourceObject != null) {
if (cost != null) {
costToPay = cost.copy();
} else {
costToPay = new GenericManaCost(genericMana.calculate(game, source, this));
}
String message;
if (chooseUseText == null) {
String effectText = executingEffects.getText(source.getModes().getMode());
message = "Pay " + cost.getText() + " to prevent (" + effectText.substring(0, effectText.length() - 1) + ")?";
message = "Pay " + costToPay.getText() + " to prevent (" + effectText.substring(0, effectText.length() - 1) + ")?";
} else {
message = chooseUseText;
}
@ -65,9 +85,10 @@ public class DoUnlessAnyPlayerPaysEffect extends OneShotEffect {
// check if any player is willing to pay
for (UUID playerId : game.getState().getPlayersInRange(controller.getId(), game)) {
Player player = game.getPlayer(playerId);
if (player != null && cost.canPay(source, source.getSourceId(), player.getId(), game) && player.chooseUse(Outcome.Detriment, message, source, game)) {
cost.clearPaid();
if (cost.pay(source, game, source.getSourceId(), player.getId(), false, null)) {
if (player != null
&& costToPay.canPay(source, source.getSourceId(), player.getId(), game) && player.chooseUse(Outcome.Detriment, message, source, game)) {
costToPay.clearPaid();
if (costToPay.pay(source, game, source.getSourceId(), player.getId(), false, null)) {
if (!game.isSimulation()) {
game.informPlayers(player.getLogName() + " pays the cost to prevent the effect");
}
@ -100,8 +121,18 @@ public class DoUnlessAnyPlayerPaysEffect extends OneShotEffect {
if (!staticText.isEmpty()) {
return staticText;
}
StringBuilder sb = new StringBuilder();
if (cost != null) {
sb.append(cost.getText());
} else {
sb.append("{X}");
}
if (genericMana != null && !genericMana.getMessage().isEmpty()) {
sb.append(", where X is ");
sb.append(genericMana.getMessage());
}
String effectsText = executingEffects.getText(mode);
return effectsText.substring(0, effectsText.length() - 1) + " unless any player pays " + cost.getText();
return effectsText.substring(0, effectsText.length() - 1) + " unless any player pays " + sb.toString();
}
@Override

View file

@ -19,7 +19,7 @@ public class DontUntapInControllersUntapStepEnchantedEffect extends ContinuousRu
}
public DontUntapInControllersUntapStepEnchantedEffect(String description) {
super(Duration.WhileOnBattlefield, Outcome.Detriment, false, true);
super(Duration.WhileOnBattlefield, Outcome.Removal, false, true);
staticText = "Enchanted " + description + " doesn't untap during its controller's untap step";
}

View file

@ -3,6 +3,7 @@ package mage.abilities.effects.common;
import mage.abilities.Ability;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.dynamicvalue.common.MultikickerCount;
import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount;
import mage.abilities.dynamicvalue.common.StaticValue;
import mage.abilities.effects.OneShotEffect;
@ -51,7 +52,7 @@ public class DrawCardSourceControllerEffect extends OneShotEffect {
private void setText() {
StringBuilder sb = new StringBuilder();
boolean oneCard = (amount instanceof StaticValue && amount.calculate(null, null, this) == 1)
|| amount instanceof PermanentsOnBattlefieldCount || amount.toString().equals("1");
|| amount instanceof PermanentsOnBattlefieldCount || amount.toString().equals("1") || amount.toString().equals("a");
sb.append("draw ").append(oneCard ? "a" : CardUtil.numberToText(amount.toString())).append(" card");
if (!oneCard) {
sb.append('s');

View file

@ -14,7 +14,7 @@ import mage.players.Player;
public class ExileAndGainLifeEqualPowerTargetEffect extends OneShotEffect {
public ExileAndGainLifeEqualPowerTargetEffect() {
super(Outcome.GainLife);
super(Outcome.Removal);
staticText = "Exile target creature. Its controller gains life equal to its power";
}

View file

@ -19,7 +19,7 @@ import mage.util.CardUtil;
public class ExileUntilSourceLeavesEffect extends OneShotEffect {
public ExileUntilSourceLeavesEffect(String targetName) {
super(Outcome.Benefit);
super(Outcome.Removal);
this.staticText = "exile target " + targetName + " an opponent controls until {this} leaves the battlefield";
}

View file

@ -1,6 +1,6 @@
package mage.abilities.effects.common;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.Mode;
import mage.abilities.effects.OneShotEffect;
@ -32,35 +32,58 @@ public class FightTargetsEffect extends OneShotEffect {
public boolean apply(Game game, Ability source) {
Card card = game.getCard(source.getSourceId());
if (card != null) {
// only if both targets are legal the effect will be applied
if (source.getTargets().get(0).isLegal(source, game) && source.getTargets().get(1).isLegal(source, game)) {
Permanent creature1 = game.getPermanent(source.getTargets().get(0).getFirstTarget());
Permanent creature2 = game.getPermanent(source.getTargets().get(1).getFirstTarget());
// 20110930 - 701.10
if (creature1 != null && creature2 != null) {
if (creature1.isCreature() && creature2.isCreature()) {
return creature1.fight(creature2, source, game);
}
UUID target1Id = null;
UUID target2Id = null;
// first target is in target pointer, second target is a normal target
if (source.getTargets().size() < 2) {
if (!source.getTargets().get(0).isLegal(source, game)) {
return false;
}
target1Id = getTargetPointer().getFirst(game, source);
target2Id = source.getTargets().getFirstTarget();
if (target1Id == target2Id) {
return false;
}
// two normal targets available, only if both targets are legal the effect will be applied
} else if (source.getTargets().get(0).isLegal(source, game) && source.getTargets().get(1).isLegal(source, game)) {
target1Id = source.getTargets().get(0).getFirstTarget();
target2Id = source.getTargets().get(1).getFirstTarget();
}
Permanent creature1 = game.getPermanent(target1Id);
Permanent creature2 = game.getPermanent(target2Id);
// 20110930 - 701.10
if (creature1 != null && creature2 != null) {
if (creature1.isCreature() && creature2.isCreature()) {
return creature1.fight(creature2, source, game);
}
}
if (!game.isSimulation()) {
game.informPlayers(card.getName() + " has been fizzled.");
}
}
if (!game.isSimulation()) {
game.informPlayers(card.getName() + " has been fizzled.");
}
return false;
}
@Override
public FightTargetsEffect copy() {
public FightTargetsEffect
copy() {
return new FightTargetsEffect(this);
}
@Override
public String getText(Mode mode) {
if (staticText != null && !staticText.isEmpty()) {
public String
getText(Mode mode
) {
if (staticText
!= null && !staticText
.isEmpty()) {
return staticText;
}
return "Target " + mode.getTargets().get(0).getTargetName() + " fights another target " + mode.getTargets().get(1).getTargetName();
return "Target " + mode
.getTargets().get(0).getTargetName() + " fights another target " + mode
.getTargets().get(1).getTargetName();
}
}

View file

@ -6,6 +6,7 @@ import java.util.UUID;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.Mode;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.effects.PreventionEffectImpl;
import mage.constants.Duration;
import mage.game.Game;
@ -27,6 +28,10 @@ public class PreventDamageToTargetMultiAmountEffect extends PreventionEffectImpl
super(duration, amount, false);
}
public PreventDamageToTargetMultiAmountEffect(Duration duration, int amount, boolean onlyCombat, boolean consumable, DynamicValue dynamicValue) {
super(duration, amount, onlyCombat, consumable, dynamicValue);
}
public PreventDamageToTargetMultiAmountEffect(final PreventDamageToTargetMultiAmountEffect effect) {
super(effect);
}
@ -100,12 +105,15 @@ public class PreventDamageToTargetMultiAmountEffect extends PreventionEffectImpl
@Override
public String getText(Mode mode) {
StringBuilder sb = new StringBuilder();
sb.append("prevent the next ").append(amountToPrevent).append(" damage that would be dealt ");
if (duration == Duration.EndOfTurn) {
sb.append("this turn ");
if (staticText.isEmpty()) {
sb.append("prevent the next ").append(amountToPrevent).append(" damage that would be dealt ");
if (duration == Duration.EndOfTurn) {
sb.append("this turn ");
}
sb.append("to any number of targets, divided as you choose");
return sb.toString();
}
sb.append("to any number of targets, divided as you choose");
return sb.toString();
return staticText;
}
}

View file

@ -1,13 +1,12 @@
package mage.abilities.effects.common;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.game.stack.Spell;
/**
*
@ -34,11 +33,9 @@ public class SacrificeSourceEffect extends OneShotEffect {
MageObject sourceObject = source.getSourceObjectIfItStillExists(game);
if (sourceObject == null) {
// Check if the effect was installed by the spell the source was cast by (e.g. Necromancy), if not don't sacrifice the permanent
if (source.getSourceObject(game) instanceof Spell) {
if (game.getState().getZone(source.getSourceId()).equals(Zone.BATTLEFIELD)
&& source.getSourceObjectZoneChangeCounter() + 1 == game.getState().getZoneChangeCounter(source.getSourceId())) {
sourceObject = game.getPermanent(source.getSourceId());
if (sourceObject != null && sourceObject.getZoneChangeCounter(game) > source.getSourceObjectZoneChangeCounter() + 1) {
return false;
}
}
}
if (sourceObject instanceof Permanent) {

View file

@ -0,0 +1,55 @@
/*
* 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;
import mage.abilities.Ability;
import mage.abilities.effects.ReplacementEffectImpl;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.game.Game;
import mage.game.events.GameEvent;
/**
*
* @author jeffwadsworth
*/
public class SkipCombatStepEffect extends ReplacementEffectImpl {
public SkipCombatStepEffect(Duration duration) {
super(duration, Outcome.Detriment);
staticText = "that player skips their next combat phase";
}
public SkipCombatStepEffect(final SkipCombatStepEffect effect) {
super(effect);
}
@Override
public SkipCombatStepEffect copy() {
return new SkipCombatStepEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
return true;
}
@Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
return true;
}
@Override
public boolean checksEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.COMBAT_PHASE;
}
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
return event.getPlayerId().equals(targetPointer.getFirst(game, source));
}
}

View file

@ -0,0 +1,55 @@
/*
* 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;
import mage.abilities.Ability;
import mage.abilities.effects.ReplacementEffectImpl;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.game.Game;
import mage.game.events.GameEvent;
/**
*
* @author jeffwadsworth
*/
public class SkipNextDrawStepTargetEffect extends ReplacementEffectImpl {
public SkipNextDrawStepTargetEffect() {
super(Duration.OneUse, Outcome.Detriment);
staticText = "Target player skips his or her next draw step";
}
public SkipNextDrawStepTargetEffect(final SkipNextDrawStepTargetEffect effect) {
super(effect);
}
@Override
public SkipNextDrawStepTargetEffect copy() {
return new SkipNextDrawStepTargetEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
return true;
}
@Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
return true;
}
@Override
public boolean checksEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.DRAW_STEP;
}
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
return event.getPlayerId().equals(source.getFirstTarget());
}
}

View file

@ -0,0 +1,74 @@
package mage.abilities.effects.common.asthought;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.effects.AsThoughEffectImpl;
import mage.cards.Card;
import mage.constants.AsThoughEffectType;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.constants.TargetController;
import mage.constants.Zone;
import mage.filter.FilterCard;
import mage.game.Game;
/**
*
* @author LevelX2
*/
public class PlayFromNotOwnHandZoneAllEffect extends AsThoughEffectImpl {
private final FilterCard filter;
private final Zone fromZone;
private final boolean onlyOwnedCards;
private final TargetController allowedCaster;
public PlayFromNotOwnHandZoneAllEffect(FilterCard filter, Zone fromZone, boolean onlyOwnedCards, TargetController allowedCaster, Duration duration) {
super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, duration, Outcome.Benefit);
this.filter = filter;
this.fromZone = fromZone;
this.onlyOwnedCards = onlyOwnedCards;
this.allowedCaster = allowedCaster;
}
public PlayFromNotOwnHandZoneAllEffect(final PlayFromNotOwnHandZoneAllEffect effect) {
super(effect);
this.filter = effect.filter;
this.fromZone = effect.fromZone;
this.onlyOwnedCards = effect.onlyOwnedCards;
this.allowedCaster = effect.allowedCaster;
}
@Override
public boolean apply(Game game, Ability source) {
return true;
}
@Override
public PlayFromNotOwnHandZoneAllEffect copy() {
return new PlayFromNotOwnHandZoneAllEffect(this);
}
@Override
public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) {
Card card = game.getCard(objectId);
if (card != null) {
switch (allowedCaster) {
case YOU:
if (affectedControllerId != source.getControllerId()) {
return false;
}
break;
case OPPONENT:
if (!game.getOpponents(source.getControllerId()).contains(affectedControllerId)) {
return false;
}
break;
}
return !onlyOwnedCards || card.getOwnerId().equals(source.getControllerId())
&& filter.match(card, game)
&& game.getState().getZone(card.getId()).match(fromZone);
}
return false;
}
}

View file

@ -0,0 +1,82 @@
package mage.abilities.effects.common.asthought;
import java.util.List;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.effects.AsThoughEffectImpl;
import mage.constants.AsThoughEffectType;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.constants.TargetController;
import mage.constants.Zone;
import mage.game.Game;
/**
*
* @author LevelX2
*/
public class PlayFromNotOwnHandZoneTargetEffect extends AsThoughEffectImpl {
private final Zone fromZone;
private final TargetController allowedCaster;
public PlayFromNotOwnHandZoneTargetEffect() {
this(Duration.EndOfTurn);
}
public PlayFromNotOwnHandZoneTargetEffect(Duration duration) {
this(Zone.ALL, TargetController.YOU, duration);
}
public PlayFromNotOwnHandZoneTargetEffect(Zone fromZone, Duration duration) {
this(fromZone, TargetController.YOU, duration);
}
public PlayFromNotOwnHandZoneTargetEffect(Zone fromZone, TargetController allowedCaster, Duration duration) {
super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, duration, Outcome.Benefit);
this.fromZone = fromZone;
this.allowedCaster = allowedCaster;
}
public PlayFromNotOwnHandZoneTargetEffect(final PlayFromNotOwnHandZoneTargetEffect effect) {
super(effect);
this.fromZone = effect.fromZone;
this.allowedCaster = effect.allowedCaster;
}
@Override
public boolean apply(Game game, Ability source) {
return true;
}
@Override
public PlayFromNotOwnHandZoneTargetEffect copy() {
return new PlayFromNotOwnHandZoneTargetEffect(this);
}
@Override
public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) {
switch (allowedCaster) {
case YOU:
if (affectedControllerId != source.getControllerId()) {
return false;
}
break;
case OPPONENT:
if (!game.getOpponents(source.getControllerId()).contains(affectedControllerId)) {
return false;
}
break;
case ANY:
break;
}
List<UUID> targets = getTargetPointer().getTargets(game, source);
if (targets.isEmpty()) {
this.discard();
return false;
}
return targets.contains(objectId)
&& affectedControllerId.equals(source.getControllerId())
&& game.getState().getZone(objectId).match(fromZone);
}
}

View file

@ -5,6 +5,7 @@ import mage.constants.AttachmentType;
import mage.constants.Duration;
import mage.abilities.Ability;
import mage.abilities.effects.RestrictionEffect;
import mage.constants.Outcome;
import mage.game.Game;
import mage.game.permanent.Permanent;
@ -14,7 +15,7 @@ import mage.game.permanent.Permanent;
public class CantAttackBlockAttachedEffect extends RestrictionEffect {
public CantAttackBlockAttachedEffect(AttachmentType attachmentType) {
super(Duration.WhileOnBattlefield);
super(Duration.WhileOnBattlefield, Outcome.Removal);
this.staticText = attachmentType.verb() + " creature can't attack or block";
}

View file

@ -0,0 +1,79 @@
/*
* 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.game.Game;
import mage.game.permanent.Permanent;
/**
*
* @author jeffwadsworth
*/
public class BecomesEnchantmentSourceEffect extends ContinuousEffectImpl implements SourceEffect {
public BecomesEnchantmentSourceEffect() {
super(Duration.Custom, Outcome.AddAbility);
staticText = "{this} becomes an Enchantment";
dependencyTypes.add(DependencyType.EnchantmentAddingRemoving);
}
public BecomesEnchantmentSourceEffect(final BecomesEnchantmentSourceEffect effect) {
super(effect);
}
@Override
public BecomesEnchantmentSourceEffect copy() {
return new BecomesEnchantmentSourceEffect(this);
}
@Override
public void init(Ability source, Game game) {
super.init(source, game);
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;
}
@Override
public boolean hasLayer(Layer layer) {
return Layer.TypeChangingEffects_4 == layer;
}
}

View file

@ -0,0 +1,52 @@
package mage.abilities.effects.common.cost;
import mage.Mana;
import mage.abilities.Ability;
import mage.abilities.SpellAbility;
import mage.constants.CostModificationType;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.game.Game;
public class SpellCostReductionSourceForOpponentsEffect extends CostModificationEffectImpl {
public SpellCostReductionSourceForOpponentsEffect() {
this("undaunted <i>(This spell costs {1} less to cast for each opponent.)</i>");
}
public SpellCostReductionSourceForOpponentsEffect(String newStaticText) {
super(Duration.Custom, Outcome.Benefit, CostModificationType.REDUCE_COST);
staticText = newStaticText;
}
public SpellCostReductionSourceForOpponentsEffect(final SpellCostReductionSourceForOpponentsEffect effect) {
super(effect);
}
@Override
public boolean apply(Game game, Ability source, Ability abilityToModify) {
SpellAbility spellAbility = (SpellAbility) abilityToModify;
Mana mana = spellAbility.getManaCostsToPay().getMana();
if (mana.getGeneric() > 0) {
int count = game.getOpponents(source.getControllerId()).size();
int newCount = mana.getGeneric() - count;
if (newCount < 0) {
newCount = 0;
}
mana.setGeneric(newCount);
spellAbility.getManaCostsToPay().load(mana.toString());
return true;
}
return false;
}
@Override
public boolean applies(Ability abilityToModify, Ability source, Game game) {
return abilityToModify instanceof SpellAbility && abilityToModify.getSourceId().equals(source.getSourceId());
}
@Override
public SpellCostReductionSourceForOpponentsEffect copy() {
return new SpellCostReductionSourceForOpponentsEffect(this);
}
}

View file

@ -1,4 +1,3 @@
package mage.abilities.effects.common.counter;
import mage.abilities.Ability;

View file

@ -1,4 +1,3 @@
package mage.abilities.keyword;
import java.util.UUID;
@ -68,16 +67,19 @@ class AftermathCastFromGraveyard extends AsThoughEffectImpl {
}
@Override
public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) {
if (objectId.equals(source.getSourceId())
public boolean applies(UUID objectId, Ability affectedAbility, Ability source, Game game, UUID affectedControllerId) {
if (affectedAbility != null && affectedAbility.getSourceId().equals(source.getSourceId())
&& affectedControllerId.equals(source.getControllerId())) {
Card card = game.getCard(source.getSourceId());
if (card != null && game.getState().getZone(source.getSourceId()) == Zone.GRAVEYARD) {
return true;
}
return game.getState().getZone(objectId).equals(Zone.GRAVEYARD);
}
return false;
}
@Override
public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) {
return false;
}
}
class AftermathCantCastFromHand extends ContinuousRuleModifyingEffectImpl {
@ -168,17 +170,14 @@ class AftermathExileAsResolvesFromGraveyard extends ReplacementEffectImpl {
@Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
UUID sourceId = source.getSourceId();
Card sourceCard = game.getCard(source.getSourceId());
if (sourceCard instanceof SplitCardHalf) {
sourceCard = ((SplitCardHalf) sourceCard).getParentCard();
sourceId = sourceCard.getId();
}
if (sourceCard != null) {
Player player = game.getPlayer(sourceCard.getOwnerId());
if (player != null) {
return player.moveCardToExileWithInfo(sourceCard, null, "", sourceId, game, ((ZoneChangeEvent) event).getFromZone(), true);
return player.moveCards(sourceCard, Zone.EXILED, source, game);
}
}
return false;

View file

@ -0,0 +1,79 @@
package mage.abilities.keyword;
import mage.abilities.StaticAbility;
import mage.constants.SubType;
import mage.constants.SuperType;
import mage.constants.Zone;
/**
*
* @author L_J
*/
public class BandsWithOtherAbility extends StaticAbility {
private SubType subtype;
private SuperType supertype;
private String bandingName;
public BandsWithOtherAbility() {
this(null, null, null);
}
public BandsWithOtherAbility(SubType subtype) {
this(subtype, null, null);
}
public BandsWithOtherAbility(SuperType supertype) {
this(null, supertype, null);
}
public BandsWithOtherAbility(String bandingName) {
this(null, null, bandingName);
}
public BandsWithOtherAbility(SubType subtype, SuperType supertype, String bandingName) {
super(Zone.ALL, null);
this.subtype = subtype;
this.supertype = supertype;
this.bandingName = bandingName;
}
public BandsWithOtherAbility(BandsWithOtherAbility ability) {
super(ability);
this.subtype = ability.subtype;
this.supertype = ability.supertype;
this.bandingName = ability.bandingName;
}
@Override
public BandsWithOtherAbility copy() {
return new BandsWithOtherAbility(this);
}
public SubType getSubtype() {
return subtype;
}
public SuperType getSupertype() {
return supertype;
}
public String getName() {
return bandingName;
}
@Override
public String getRule() {
StringBuilder sb = new StringBuilder("bands with other");
if (subtype != null) {
return sb.append(' ').append(subtype.getDescription()).append('s').toString();
} else if (supertype != null) {
return sb.append(' ').append(supertype.toString()).append(" creatures").toString();
} else if (bandingName != null) {
return sb.append(" creatures named ").append(bandingName).toString();
}
return "all \"" + sb.toString() + "\" abilities";
}
}

View file

@ -13,7 +13,6 @@ import mage.game.permanent.Permanent;
import mage.target.common.TargetCreaturePermanent;
/**
*
* @author TheElk801
*/
public class MentorAbility extends AttacksTriggeredAbility {
@ -22,7 +21,7 @@ public class MentorAbility extends AttacksTriggeredAbility {
static {
filter.add(new AttackingPredicate());
filter.add(new MentorAbilityPredicate());
filter.add(MentorAbilityPredicate.instance);
}
public MentorAbility() {
@ -46,7 +45,8 @@ public class MentorAbility extends AttacksTriggeredAbility {
}
class MentorAbilityPredicate implements ObjectSourcePlayerPredicate<ObjectSourcePlayer<Card>> {
enum MentorAbilityPredicate implements ObjectSourcePlayerPredicate<ObjectSourcePlayer<Card>> {
instance;
@Override
public boolean apply(ObjectSourcePlayer<Card> input, Game game) {

View file

@ -199,8 +199,8 @@ class RevealNinjutsuCardCost extends CostImpl {
for (CommandObject coj : game.getState().getCommand()) {
if (coj != null && coj.getId().equals(ability.getSourceId())) {
card = game.getCard(ability.getSourceId());
break;
}
break;
}
}
if (card != null) {

View file

@ -5,25 +5,17 @@
*/
package mage.abilities.keyword;
import mage.Mana;
import mage.abilities.Ability;
import mage.abilities.SpellAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.common.cost.CostModificationEffectImpl;
import mage.constants.CostModificationType;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.abilities.effects.common.cost.SpellCostReductionSourceForOpponentsEffect;
import mage.constants.Zone;
import mage.game.Game;
/**
*
* @author LevelX2
*/
public class UndauntedAbility extends SimpleStaticAbility {
public UndauntedAbility() {
super(Zone.ALL, new UndauntedEffect());
super(Zone.ALL, new SpellCostReductionSourceForOpponentsEffect("undaunted <i>(This spell costs {1} less to cast for each opponent.)</i>"));
setRuleAtTheTop(true);
}
@ -36,43 +28,4 @@ public class UndauntedAbility extends SimpleStaticAbility {
return new UndauntedAbility(this);
}
}
class UndauntedEffect extends CostModificationEffectImpl {
public UndauntedEffect() {
super(Duration.Custom, Outcome.Benefit, CostModificationType.REDUCE_COST);
staticText = "undaunted <i>(This spell costs {1} less to cast for each opponent.)</i>";
}
public UndauntedEffect(final UndauntedEffect effect) {
super(effect);
}
@Override
public boolean apply(Game game, Ability source, Ability abilityToModify) {
SpellAbility spellAbility = (SpellAbility) abilityToModify;
Mana mana = spellAbility.getManaCostsToPay().getMana();
if (mana.getGeneric() > 0) {
int count = game.getOpponents(source.getControllerId()).size();
int newCount = mana.getGeneric() - count;
if (newCount < 0) {
newCount = 0;
}
mana.setGeneric(newCount);
spellAbility.getManaCostsToPay().load(mana.toString());
return true;
}
return false;
}
@Override
public boolean applies(Ability abilityToModify, Ability source, Game game) {
return abilityToModify instanceof SpellAbility && abilityToModify.getSourceId().equals(source.getSourceId());
}
@Override
public UndauntedEffect copy() {
return new UndauntedEffect(this);
}
}
}

View file

@ -1,6 +1,5 @@
package mage.abilities.meta;
import mage.MageObject;
import mage.abilities.TriggeredAbility;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.Effect;
@ -14,9 +13,12 @@ import java.util.List;
import java.util.UUID;
/**
* A triggered ability that combines several others and triggers whenever one or more of them would. The abilities
* passed in should have null as their effect, and should have their own targets set if necessary. All other information
* will be passed in from changes to this Ability. Note: this does NOT work with abilities that have intervening if clauses.
* A triggered ability that combines several others and triggers whenever one or
* more of them would. The abilities passed in should have null as their effect,
* and should have their own targets set if necessary. All other information
* will be passed in from changes to this Ability. Note: this does NOT work with
* abilities that have intervening if clauses.
*
* @author noahg
*/
public class OrTriggeredAbility extends TriggeredAbilityImpl {
@ -43,18 +45,17 @@ public class OrTriggeredAbility extends TriggeredAbilityImpl {
public OrTriggeredAbility(OrTriggeredAbility ability) {
super(ability);
this.triggeredAbilities = new TriggeredAbility[ability.triggeredAbilities.length];
for (int i = 0; i < this.triggeredAbilities.length; i++){
for (int i = 0; i < this.triggeredAbilities.length; i++) {
this.triggeredAbilities[i] = ability.triggeredAbilities[i].copy();
}
this.triggeringAbilities = new ArrayList<>(ability.triggeringAbilities);
this.ruleTrigger = ability.ruleTrigger;
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
for (TriggeredAbility ability : triggeredAbilities) {
if (ability.checkEventType(event, game)){
if (ability.checkEventType(event, game)) {
System.out.println("Correct event type (" + event.getType() + ")");
return true;
}
@ -101,7 +102,6 @@ public class OrTriggeredAbility extends TriggeredAbilityImpl {
return sb.toString() + super.getRule();
}
@Override
public void setControllerId(UUID controllerId) {
super.setControllerId(controllerId);
@ -126,11 +126,4 @@ public class OrTriggeredAbility extends TriggeredAbilityImpl {
}
}
@Override
public void setSourceObject(MageObject sourceObject, Game game) {
super.setSourceObject(sourceObject, game);
for (TriggeredAbility ability : triggeredAbilities) {
ability.setSourceObject(sourceObject, game);
}
}
}

View file

@ -6,7 +6,7 @@ public final class CardGraphicInfo {
private final ObjectColor frameColor;
private final FrameStyle frameStyle;
private final boolean useVariousArt;
private final boolean useVariousArt; // card in set have multiple images (use to store images files)
public CardGraphicInfo(FrameStyle frameStyle, boolean useVariousArt) {
this(null, frameStyle, useVariousArt);

View file

@ -21,12 +21,7 @@ public final class CardSetInfo implements Serializable {
this.expansionSetCode = expansionSetCode;
this.cardNumber = cardNumber;
this.rarity = rarity;
if (graphicInfo == null && Rarity.LAND == rarity) {
// Workaround to get images of basic land permanents loaded
this.graphicInfo = ExpansionSet.NON_FULL_USE_VARIOUS;
} else {
this.graphicInfo = graphicInfo;
}
this.graphicInfo = graphicInfo;
}
public String getName() {

View file

@ -88,6 +88,8 @@ public abstract class ExpansionSet implements Serializable {
protected Date releaseDate;
protected ExpansionSet parentSet;
protected SetType setType;
// TODO: 03.10.2018, hasBasicLands can be removed someday -- it's uses to optimize lands search in deck generation and lands adding (search all available lands from sets)
protected boolean hasBasicLands = true;
protected String blockName;
@ -559,6 +561,7 @@ public abstract class ExpansionSet implements Serializable {
if (savedCardsInfos == null) {
CardCriteria criteria = new CardCriteria();
if (rarity == Rarity.LAND) {
// get basic lands from parent set if current haven't it
criteria.setCodes(!hasBasicLands && parentSet != null ? parentSet.code : this.code);
} else {
criteria.setCodes(this.code);

View file

@ -39,7 +39,9 @@ public final class DeckImporterUtil {
}
public static DeckImporter getDeckImporter(String file) {
if (file.toLowerCase(Locale.ENGLISH).endsWith("dec")) {
if (file == null) {
return null;
} if (file.toLowerCase(Locale.ENGLISH).endsWith("dec")) {
return new DecDeckImporter();
} else if (file.toLowerCase(Locale.ENGLISH).endsWith("mwdeck")) {
return new MWSDeckImporter();

View file

@ -32,7 +32,7 @@ public enum CardRepository {
// raise this if db structure was changed
private static final long CARD_DB_VERSION = 51;
// raise this if new cards were added to the server
private static final long CARD_CONTENT_VERSION = 120;
private static final long CARD_CONTENT_VERSION = 122;
private Dao<CardInfo, Object> cardDao;
private Set<String> classNames;

View file

@ -33,6 +33,8 @@ public enum ExpansionRepository {
private Dao<ExpansionInfo, Object> expansionDao;
public boolean instanceInitialized = false;
ExpansionRepository() {
File file = new File("db");
if (!file.exists()) {
@ -48,9 +50,11 @@ public enum ExpansionRepository {
TableUtils.createTableIfNotExists(connectionSource, ExpansionInfo.class);
expansionDao = DaoManager.createDao(connectionSource, ExpansionInfo.class);
instanceInitialized = true;
} catch (SQLException ex) {
ex.printStackTrace();
}
}
public void add(ExpansionInfo expansion) {

View file

@ -1,29 +1,30 @@
package mage.constants;
/**
*
* @author North
*/
public enum Rarity {
LAND ("Land", "common", "C", 1),
COMMON ("Common", "common", "C", 1),
UNCOMMON ("Uncommon", "uncommon", "U", 2),
RARE ("Rare", "rare", "R", 3),
MYTHIC ("Mythic", "mythic", "M", 3),
SPECIAL ("Special", "special", "Special", 3),
BONUS ("Bonus", "bonus", "Bonus", 3);
LAND("Land", "common", "C", 1, 1),
COMMON("Common", "common", "C", 1, 2),
UNCOMMON("Uncommon", "uncommon", "U", 2, 3),
RARE("Rare", "rare", "R", 3, 4),
MYTHIC("Mythic", "mythic", "M", 3, 5),
SPECIAL("Special", "special", "Special", 3, 6),
BONUS("Bonus", "bonus", "Bonus", 3, 7);
private final String text;
private final String symbolCode;
private final String code;
private final int rating;
private final int sorting;
Rarity(String text, String symbolCode, String code, int rating) {
Rarity(String text, String symbolCode, String code, int rating, int sorting) {
this.text = text;
this.symbolCode = symbolCode;
this.code = code;
this.rating = rating;
this.sorting = sorting;
}
@Override
@ -42,4 +43,8 @@ public enum Rarity {
public int getRating() {
return rating;
}
public int getSorting() {
return sorting;
}
}

View file

@ -93,6 +93,7 @@ public enum SubType {
CHIMERA("Chimera", SubTypeSet.CreatureType),
CHISS("Chiss", SubTypeSet.CreatureType, true),
CITIZEN("Citizen", SubTypeSet.CreatureType),
CLAMFOLK("Clamfolk", SubTypeSet.CreatureType, true), // Unglued
CLERIC("Cleric", SubTypeSet.CreatureType),
COCKATRICE("Cockatrice", SubTypeSet.CreatureType),
CONSTRUCT("Construct", SubTypeSet.CreatureType),
@ -131,6 +132,7 @@ public enum SubType {
ELK("Elk", SubTypeSet.CreatureType),
EYE("Eye", SubTypeSet.CreatureType),
EWOK("Ewok", SubTypeSet.CreatureType, true), // Star Wars
EXPANSION_SYMBOL("Expansion-Symbol", SubTypeSet.CreatureType, true), // Unhinged
// F
FAERIE("Faerie", SubTypeSet.CreatureType),
FERRET("Ferret", SubTypeSet.CreatureType),

View file

@ -9,6 +9,7 @@ public enum CounterType {
AGE("age"),
AIM("aim"),
ARROW("arrow"),
ARROWHEAD("arrowhead"),
AWAKENING("awakening"),
BLAZE("blaze"),
@ -19,6 +20,7 @@ public enum CounterType {
CAGE("cage"),
CARRION("carrion"),
CHARGE("charge"),
CHIP("chip"),
CORPSE("corpse"),
CREDIT("credit"),
CRYSTAL("crystal"),
@ -44,6 +46,7 @@ public enum CounterType {
FEATHER("feather"),
FILIBUSTER("filibuster"),
FLOOD("flood"),
FUNK("funk"),
FURY("fury"),
FUNGUS("fungus"),
FUSE("fuse"),

View file

@ -23,7 +23,7 @@ import mage.filter.predicate.permanent.TokenPredicate;
/**
* A class that holds Filter objects that may not be modified without copying
* before. This prevents the creation of thousands of filter objects.
*
* <p>
* Because the filters are used application wide they may not be modified.
* NEVER!!!!! But it's possible, so be careful!
*
@ -42,6 +42,7 @@ public final class StaticFilters {
static {
FILTER_ENCHANTMENT_PERMANENT.setLockedFilter(true);
}
public static final FilterCard FILTER_CARD = new FilterCard("card");
static {
@ -119,6 +120,7 @@ public final class StaticFilters {
static {
FILTER_CARD_NON_LAND.setLockedFilter(true);
}
public static final FilterNonlandCard FILTER_CARD_A_NON_LAND = new FilterNonlandCard("a nonland card");
static {
@ -154,11 +156,13 @@ public final class StaticFilters {
static {
FILTER_PERMANENT_ARTIFACT_OR_ENCHANTMENT.setLockedFilter(true);
}
public static final FilterCreaturePermanent FILTER_ARTIFACT_CREATURE_PERMANENT = new FilterArtifactCreaturePermanent();
static {
FILTER_ARTIFACT_CREATURE_PERMANENT.setLockedFilter(true);
}
public static final FilterPermanent FILTER_PERMANENT_ARTIFACT_OR_CREATURE = new FilterPermanent("artifact or creature");
static {
@ -168,6 +172,7 @@ public final class StaticFilters {
));
FILTER_PERMANENT_ARTIFACT_OR_CREATURE.setLockedFilter(true);
}
public static final FilterPermanent FILTER_PERMANENT_ARTIFACT_CREATURE_OR_ENCHANTMENT = new FilterPermanent("artifact, creature, or enchantment");
static {
@ -178,6 +183,7 @@ public final class StaticFilters {
));
FILTER_PERMANENT_ARTIFACT_CREATURE_OR_ENCHANTMENT.setLockedFilter(true);
}
public static final FilterPermanent FILTER_PERMANENT_ARTIFACT_CREATURE_ENCHANTMENT_OR_LAND = new FilterPermanent("artifact, creature, enchantment, or land");
static {
@ -195,16 +201,19 @@ public final class StaticFilters {
static {
FILTER_CONTROLLED_PERMANENT.setLockedFilter(true);
}
public static final FilterControlledPermanent FILTER_CONTROLLED_PERMANENT_ARTIFACT = new FilterControlledArtifactPermanent();
static {
FILTER_CONTROLLED_PERMANENT_ARTIFACT.setLockedFilter(true);
}
public static final FilterControlledPermanent FILTER_CONTROLLED_PERMANENT_ARTIFACT_AN = new FilterControlledArtifactPermanent("an artifact");
static {
FILTER_CONTROLLED_PERMANENT_ARTIFACT_AN.setLockedFilter(true);
}
public static final FilterControlledPermanent FILTER_CONTROLLED_PERMANENT_ARTIFACT_OR_CREATURE = new FilterControlledPermanent("artifact or creature you control");
static {
@ -214,6 +223,7 @@ public final class StaticFilters {
));
FILTER_CONTROLLED_PERMANENT_ARTIFACT_OR_CREATURE.setLockedFilter(true);
}
public static final FilterControlledPermanent FILTER_CONTROLLED_PERMANENT_LAND = new FilterControlledLandPermanent();
static {
@ -232,12 +242,14 @@ public final class StaticFilters {
FILTER_OPPONENTS_PERMANENT.add(new ControllerPredicate(TargetController.OPPONENT));
FILTER_OPPONENTS_PERMANENT.setLockedFilter(true);
}
public static final FilterCreaturePermanent FILTER_OPPONENTS_PERMANENT_CREATURE = new FilterCreaturePermanent("creature an opponent controls");
static {
FILTER_OPPONENTS_PERMANENT_CREATURE.add(new ControllerPredicate(TargetController.OPPONENT));
FILTER_OPPONENTS_PERMANENT_CREATURE.setLockedFilter(true);
}
public static final FilterPermanent FILTER_OPPONENTS_PERMANENT_ARTIFACT = new FilterPermanent("artifact an opponent controls");
static {
@ -245,6 +257,7 @@ public final class StaticFilters {
FILTER_OPPONENTS_PERMANENT_ARTIFACT.add(new CardTypePredicate(CardType.ARTIFACT));
FILTER_OPPONENTS_PERMANENT_ARTIFACT.setLockedFilter(true);
}
public static final FilterPermanent FILTER_OPPONENTS_PERMANENT_ARTIFACT_OR_CREATURE = new FilterPermanent("artifact or creature an opponent controls");
static {
@ -280,12 +293,14 @@ public final class StaticFilters {
static {
FILTER_CONTROLLED_A_CREATURE.setLockedFilter(true);
}
public static final FilterControlledCreaturePermanent FILTER_CONTROLLED_ANOTHER_CREATURE = new FilterControlledCreaturePermanent("another creature");
static {
FILTER_CONTROLLED_ANOTHER_CREATURE.add(new AnotherPredicate());
FILTER_CONTROLLED_ANOTHER_CREATURE.setLockedFilter(true);
}
public static final FilterControlledPermanent FILTER_CONTROLLED_PERMANENT_NON_LAND = new FilterControlledPermanent("nonland permanent");
static {
@ -294,6 +309,7 @@ public final class StaticFilters {
);
FILTER_CONTROLLED_PERMANENT_NON_LAND.setLockedFilter(true);
}
public static final FilterLandPermanent FILTER_LAND = new FilterLandPermanent();
static {
@ -331,6 +347,12 @@ public final class StaticFilters {
FILTER_PERMANENT_CREATURE.setLockedFilter(true);
}
public static final FilterCreaturePermanent FILTER_PERMANENT_CREATURE_A = new FilterCreaturePermanent("a creature");
static {
FILTER_PERMANENT_CREATURE_A.setLockedFilter(true);
}
public static final FilterPermanent FILTER_PERMANENT_CREATURE_OR_PLANESWALKER_A = new FilterPermanent("a creature or planeswalker");
static {
@ -344,33 +366,39 @@ public final class StaticFilters {
static {
FILTER_PERMANENT_A_CREATURE.setLockedFilter(true);
}
public static final FilterCreaturePermanent FILTER_PERMANENT_CREATURE_CONTROLLED = new FilterCreaturePermanent("creature you control");
static {
FILTER_PERMANENT_CREATURE_CONTROLLED.add(new ControllerPredicate(TargetController.YOU));
FILTER_PERMANENT_CREATURE_CONTROLLED.setLockedFilter(true);
}
public static final FilterCreaturePermanent FILTER_PERMANENT_CREATURES = new FilterCreaturePermanent("creatures");
static {
FILTER_PERMANENT_CREATURES.setLockedFilter(true);
}
public static final FilterCreaturePermanent FILTER_PERMANENT_CREATURES_CONTROLLED = new FilterCreaturePermanent("creatures you control");
static {
FILTER_PERMANENT_CREATURES_CONTROLLED.add(new ControllerPredicate(TargetController.YOU));
FILTER_PERMANENT_CREATURES_CONTROLLED.setLockedFilter(true);
}
public static final FilterCreaturePermanent FILTER_PERMANENT_CREATURE_GOBLINS = new FilterCreaturePermanent(SubType.GOBLIN, "Goblin creatures");
static {
FILTER_PERMANENT_CREATURE_GOBLINS.setLockedFilter(true);
}
public static final FilterCreaturePermanent FILTER_PERMANENT_CREATURE_SLIVERS = new FilterCreaturePermanent(SubType.SLIVER, "all Sliver creatures");
static {
FILTER_PERMANENT_CREATURE_SLIVERS.setLockedFilter(true);
}
public static final FilterPlaneswalkerPermanent FILTER_PERMANENT_PLANESWALKER = new FilterPlaneswalkerPermanent();
static {
@ -388,12 +416,14 @@ public final class StaticFilters {
static {
FILTER_PERMANENTS_NON_LAND.setLockedFilter(true);
}
public static final FilterStackObject FILTER_SPELL_OR_ABILITY_OPPONENTS = new FilterStackObject("spell or ability and opponent controls");
static {
FILTER_SPELL_OR_ABILITY_OPPONENTS.add(new ControllerPredicate(TargetController.OPPONENT));
FILTER_SPELL_OR_ABILITY_OPPONENTS.setLockedFilter(true);
}
public static final FilterStackObject FILTER_SPELL_OR_ABILITY = new FilterStackObject();
static {
@ -405,11 +435,13 @@ public final class StaticFilters {
static {
FILTER_SPELL_A_CREATURE.setLockedFilter(true);
}
public static final FilterCreatureSpell FILTER_SPELL_CREATURE = new FilterCreatureSpell("creature spell");
static {
FILTER_SPELL_CREATURE.setLockedFilter(true);
}
public static final FilterSpell FILTER_SPELL_NON_CREATURE = (FilterSpell) new FilterSpell("noncreature spell").add(Predicates.not(new CardTypePredicate(CardType.CREATURE)));
static {
@ -444,6 +476,7 @@ public final class StaticFilters {
));
FILTER_SPELL_AN_INSTANT_OR_SORCERY.setLockedFilter(true);
}
public static final FilterSpell FILTER_SPELL_INSTANT_OR_SORCERY = new FilterSpell("instant or sorcery spell");
static {
@ -463,6 +496,7 @@ public final class StaticFilters {
));
FILTER_SPELLS_INSTANT_OR_SORCERY.setLockedFilter(true);
}
public static final FilterCreaturePermanent FILTER_CREATURE_TOKENS = new FilterCreaturePermanent("creature tokens");
static {
@ -484,6 +518,7 @@ public final class StaticFilters {
FILTER_PERMANENT_AURA.add(new SubtypePredicate(SubType.AURA));
FILTER_PERMANENT_AURA.setLockedFilter(true);
}
public static final FilterPermanent FILTER_PERMANENT_EQUIPMENT = new FilterPermanent();
static {
@ -491,6 +526,7 @@ public final class StaticFilters {
FILTER_PERMANENT_EQUIPMENT.add(new SubtypePredicate(SubType.EQUIPMENT));
FILTER_PERMANENT_EQUIPMENT.setLockedFilter(true);
}
public static final FilterPermanent FILTER_PERMANENT_FORTIFICATION = new FilterPermanent();
static {
@ -498,6 +534,7 @@ public final class StaticFilters {
FILTER_PERMANENT_FORTIFICATION.add(new SubtypePredicate(SubType.FORTIFICATION));
FILTER_PERMANENT_FORTIFICATION.setLockedFilter(true);
}
public static final FilterPermanent FILTER_PERMANENT_LEGENDARY = new FilterPermanent();
static {

View file

@ -12,6 +12,9 @@ public interface Controllable {
UUID getId();
default boolean isControlledBy(UUID controllerID){
if(getControllerId() == null){
return false;
}
return getControllerId().equals(controllerID);
}
}

View file

@ -1026,6 +1026,7 @@ public abstract class GameImpl implements Game, Serializable {
watchers.add(new BlockedAttackerWatcher());
watchers.add(new DamageDoneWatcher());
watchers.add(new PlanarRollWatcher());
watchers.add(new PlayersAttackedThisTurnWatcher());
//20100716 - 103.5
for (UUID playerId : state.getPlayerList(startingPlayerId)) {
@ -1527,7 +1528,7 @@ public abstract class GameImpl implements Game, Serializable {
@Override
public void addEffect(ContinuousEffect continuousEffect, Ability source) {
Ability newAbility = source.copy();
newAbility.setSourceObject(null, this); // Update the source object to the currently existing Object
newAbility.setSourceObjectZoneChangeCounter(getState().getZoneChangeCounter(source.getSourceId()));
ContinuousEffect newEffect = continuousEffect.copy();
newEffect.newId();
@ -1698,11 +1699,17 @@ public abstract class GameImpl implements Game, Serializable {
if (ability instanceof TriggeredManaAbility || ability instanceof DelayedTriggeredManaAbility) {
// 20110715 - 605.4
Ability manaAbiltiy = ability.copy();
if (manaAbiltiy.getSourceObjectZoneChangeCounter() == 0) {
manaAbiltiy.setSourceObjectZoneChangeCounter(getState().getZoneChangeCounter(ability.getSourceId()));
}
manaAbiltiy.activate(this, false);
manaAbiltiy.resolve(this);
} else {
TriggeredAbility newAbility = ability.copy();
newAbility.newId();
if (newAbility.getSourceObjectZoneChangeCounter() == 0) {
newAbility.setSourceObjectZoneChangeCounter(getState().getZoneChangeCounter(ability.getSourceId()));
}
state.addTriggeredAbility(newAbility);
}
}
@ -1711,10 +1718,10 @@ public abstract class GameImpl implements Game, Serializable {
public UUID addDelayedTriggeredAbility(DelayedTriggeredAbility delayedAbility, Ability source) {
delayedAbility.setSourceId(source.getSourceId());
delayedAbility.setControllerId(source.getControllerId());
delayedAbility.setSourceObject(source.getSourceObject(this), this);
// return addDelayedTriggeredAbility(delayedAbility);
DelayedTriggeredAbility newAbility = delayedAbility.copy();
newAbility.newId();
newAbility.setSourceObjectZoneChangeCounter(getState().getZoneChangeCounter(source.getSourceId()));
newAbility.initOnAdding(this);
// ability.init is called as the ability triggeres not now.
// If a FixedTarget pointer is already set from the effect setting up this delayed ability
@ -2594,7 +2601,7 @@ public abstract class GameImpl implements Game, Serializable {
boolean addPlaneAgain = false;
for (Iterator<CommandObject> it = this.getState().getCommand().iterator(); it.hasNext();) {
CommandObject obj = it.next();
if (obj.getControllerId().equals(playerId)) {
if (obj.isControlledBy(playerId)) {
if (obj instanceof Emblem) {
((Emblem) obj).discardEffects();// This may not be the best fix but it works
}

View file

@ -1,4 +1,3 @@
package mage.game.combat;
import mage.MageObject;
@ -6,6 +5,7 @@ import mage.abilities.Ability;
import mage.abilities.effects.RequirementEffect;
import mage.abilities.effects.RestrictionEffect;
import mage.abilities.keyword.BandingAbility;
import mage.abilities.keyword.BandsWithOtherAbility;
import mage.abilities.keyword.VigilanceAbility;
import mage.abilities.keyword.special.JohanVigilanceAbility;
import mage.constants.Outcome;
@ -14,8 +14,12 @@ import mage.filter.StaticFilters;
import mage.filter.common.FilterControlledCreaturePermanent;
import mage.filter.common.FilterCreatureForCombatBlock;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.Predicate;
import mage.filter.predicate.Predicates;
import mage.filter.predicate.mageobject.AbilityPredicate;
import mage.filter.predicate.mageobject.NamePredicate;
import mage.filter.predicate.mageobject.SubtypePredicate;
import mage.filter.predicate.mageobject.SupertypePredicate;
import mage.filter.predicate.permanent.AttackingSameNotBandedPredicate;
import mage.filter.predicate.permanent.PermanentIdPredicate;
import mage.game.Game;
@ -103,8 +107,8 @@ public class Combat implements Serializable, Copyable<Combat> {
}
/**
* Get all possible defender (players and plainwalkers) That does not mean
* neccessarly mean that they are really attacked
* Get all possible defender (players and planeswalkers) That does not mean
* necessarily mean that they are really attacked
*
* @return
*/
@ -246,11 +250,14 @@ public class Combat implements Serializable, Copyable<Combat> {
game.getCombat().checkAttackRequirements(player, game);
boolean firstTime = true;
do {
if (!firstTime || !game.getPlayer(game.getActivePlayerId()).getAvailableAttackers(game).isEmpty()) {
if (!firstTime
|| !game.getPlayer(game.getActivePlayerId()).getAvailableAttackers(game).isEmpty()) {
player.selectAttackers(game, attackingPlayerId);
}
firstTime = false;
if (game.isPaused() || game.checkIfGameIsOver() || game.executingRollback()) {
if (game.isPaused()
|| game.checkIfGameIsOver()
|| game.executingRollback()) {
return;
}
// because of possible undo during declare attackers it's neccassary to call here the methods with "game.getCombat()." to get the current combat object!!!
@ -293,53 +300,115 @@ public class Combat implements Serializable, Copyable<Combat> {
Permanent attacker = game.getPermanent(creatureId);
if (attacker != null && player != null) {
CombatGroup combatGroup = findGroup(attacker.getId());
if (combatGroup != null && attacker.getAbilities().containsKey(BandingAbility.getInstance().getId()) && attacker.getBandedCards().isEmpty() && getAttackers().size() > 1) {
boolean isBanded = false;
FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("attacking creature to band with " + attacker.getLogName());
filter.add(Predicates.not(new PermanentIdPredicate(creatureId)));
filter.add(new AttackingSameNotBandedPredicate(combatGroup.getDefenderId())); // creature that isn't already banded, and is attacking the same player or planeswalker
while (player.canRespond()) {
TargetControlledPermanent target = new TargetControlledPermanent(1, 1, filter, true);
target.setRequired(false);
if (!target.canChoose(attackingPlayerId, game)
|| game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.DECLARING_ATTACKERS, attackingPlayerId, attackingPlayerId))
|| !player.chooseUse(Outcome.Benefit, "Do you wish to " + (isBanded ? "band " + attacker.getLogName() + " with another " : "form a band with " + attacker.getLogName() + " and an ") + "attacking creature?", null, game)) {
break;
}
if (target.choose(Outcome.Benefit, attackingPlayerId, null, game)) {
isBanded = true;
for (UUID targetId : target.getTargets()) {
Permanent permanent = game.getPermanent(targetId);
if (permanent != null) {
for (UUID bandedId : attacker.getBandedCards()) {
permanent.addBandedCard(bandedId);
Permanent banded = game.getPermanent(bandedId);
if (banded != null) {
banded.addBandedCard(targetId);
}
}
permanent.addBandedCard(creatureId);
attacker.addBandedCard(targetId);
if (!permanent.getAbilities().containsKey(BandingAbility.getInstance().getId())) {
filter.add(new AbilityPredicate(BandingAbility.class));
}
}
}
if (combatGroup != null && attacker.getBandedCards().isEmpty() && getAttackers().size() > 1) {
boolean canBand = attacker.getAbilities().containsKey(BandingAbility.getInstance().getId());
List<Ability> bandsWithOther = new ArrayList<>();
for (Ability ability : attacker.getAbilities()) {
if (ability.getClass().equals(BandsWithOtherAbility.class)) {
bandsWithOther.add(ability);
}
}
if (isBanded) {
StringBuilder sb = new StringBuilder(player.getLogName()).append(" formed a band with ").append((attacker.getBandedCards().size() + 1) + " creatures: ");
sb.append(attacker.getLogName());
for (UUID id : attacker.getBandedCards()) {
sb.append(", ");
Permanent permanent = game.getPermanent(id);
if (permanent != null) {
sb.append(permanent.getLogName());
boolean canBandWithOther = !bandsWithOther.isEmpty();
if (canBand || canBandWithOther) {
boolean isBanded = false;
FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("attacking creature to band with " + attacker.getLogName());
filter.add(Predicates.not(new PermanentIdPredicate(creatureId)));
filter.add(new AttackingSameNotBandedPredicate(combatGroup.getDefenderId())); // creature that isn't already banded, and is attacking the same player or planeswalker
List<Predicate<MageObject>> predicates = new ArrayList<>();
if (!canBand && canBandWithOther) {
for (Ability ab : bandsWithOther) {
BandsWithOtherAbility ability = (BandsWithOtherAbility) ab;
if (ability.getSubtype() != null) {
predicates.add(new SubtypePredicate(ability.getSubtype()));
}
if (ability.getSupertype() != null) {
predicates.add(new SupertypePredicate(ability.getSupertype()));
}
if (ability.getName() != null) {
predicates.add(new NamePredicate(ability.getName()));
}
}
filter.add(Predicates.or(predicates));
}
while (player.canRespond()) {
TargetControlledPermanent target = new TargetControlledPermanent(1, 1, filter, true);
target.setRequired(false);
canBand &= target.canChoose(attackingPlayerId, game);
canBandWithOther &= target.canChoose(attackingPlayerId, game);
if (game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.DECLARING_ATTACKERS, attackingPlayerId, attackingPlayerId))
|| (!canBand && !canBandWithOther)
|| !player.chooseUse(Outcome.Benefit, "Do you wish to " + (isBanded ? "band " + attacker.getLogName() + " with another " : "form a band with " + attacker.getLogName() + " and an ") + "attacking creature?", null, game)) {
break;
}
if (canBand && canBandWithOther) {
if (player.chooseUse(Outcome.Detriment, "Choose type of banding ability to apply:", attacker.getLogName(), "Banding", "Bands with other", null, game)) {
canBandWithOther = false;
} else {
canBand = false;
for (Ability ab : bandsWithOther) {
BandsWithOtherAbility ability = (BandsWithOtherAbility) ab;
if (ability.getSubtype() != null) {
predicates.add(new SubtypePredicate(ability.getSubtype()));
}
if (ability.getSupertype() != null) {
predicates.add(new SupertypePredicate(ability.getSupertype()));
}
if (ability.getName() != null) {
predicates.add(new NamePredicate(ability.getName()));
}
}
filter.add(Predicates.or(predicates));
}
}
if (target.choose(Outcome.Benefit, attackingPlayerId, null, game)) {
isBanded = true;
for (UUID targetId : target.getTargets()) {
Permanent permanent = game.getPermanent(targetId);
if (permanent != null) {
for (UUID bandedId : attacker.getBandedCards()) {
permanent.addBandedCard(bandedId);
Permanent banded = game.getPermanent(bandedId);
if (banded != null) {
banded.addBandedCard(targetId);
}
}
permanent.addBandedCard(creatureId);
attacker.addBandedCard(targetId);
if (canBand) {
if (!permanent.getAbilities().containsKey(BandingAbility.getInstance().getId())) {
filter.add(new AbilityPredicate(BandingAbility.class));
canBandWithOther = false;
}
} else if (canBandWithOther) {
List<Predicate<MageObject>> newPredicates = new ArrayList<>();
for (Predicate<MageObject> predicate : predicates) {
if (predicate.apply(permanent, game)) {
newPredicates.add(predicate);
}
}
filter.add(Predicates.or(newPredicates));
canBand = false;
}
}
}
}
}
game.informPlayers(sb.toString());
if (isBanded) {
StringBuilder sb = new StringBuilder(player.getLogName()).append(" formed a band with ").append((attacker.getBandedCards().size() + 1) + " creatures: ");
sb.append(attacker.getLogName());
for (UUID id : attacker.getBandedCards()) {
sb.append(", ");
Permanent permanent = game.getPermanent(id);
if (permanent != null) {
sb.append(permanent.getLogName());
}
}
game.informPlayers(sb.toString());
}
}
}
}
@ -387,11 +456,16 @@ public class Combat implements Serializable, Copyable<Combat> {
if (defenders.size() == 1) {
player.declareAttacker(creature.getId(), defenders.iterator().next(), game, false);
} else {
TargetDefender target = new TargetDefender(defenders, creature.getId());
target.setRequired(true);
target.setTargetName("planeswalker or player for " + creature.getLogName() + " to attack");
if (player.chooseTarget(Outcome.Damage, target, null, game)) {
player.declareAttacker(creature.getId(), target.getFirstTarget(), game, false);
if (!player.isHuman()) { // computer only for multiple defenders
player.declareAttacker(creature.getId(), defenders.iterator().next(), game, false);
} else { // human players only for multiple defenders
TargetDefender target = new TargetDefender(defenders, creature.getId());
target.setRequired(true);
target.setTargetName("planeswalker or player for " + creature.getLogName() + " to attack");
if (player.chooseTarget(Outcome.Damage, target, null, game)) {
System.out.println("The player " + player.getName() + " declares an attacker here. " + creature.getName());
player.declareAttacker(creature.getId(), target.getFirstTarget(), game, false);
}
}
}
} else {
@ -483,7 +557,7 @@ public class Combat implements Serializable, Copyable<Combat> {
* Handle the blocker selection process
*
* @param blockController player that controlls how to block, if null the
* defender is the controller
* defender is the controller
* @param game
*/
public void selectBlockers(Player blockController, Game game) {
@ -1275,10 +1349,10 @@ public class Combat implements Serializable, Copyable<Combat> {
if (defenderAttackedBy.size() >= defendingPlayer.getMaxAttackedBy()) {
Player attackingPlayer = game.getPlayer(game.getControllerId(attackerId));
if (attackingPlayer != null && !game.isSimulation()) {
game.informPlayer(attackingPlayer, "No more than " +
CardUtil.numberToText(defendingPlayer.getMaxAttackedBy()) +
" creatures can attack " +
defendingPlayer.getLogName());
game.informPlayer(attackingPlayer, "No more than "
+ CardUtil.numberToText(defendingPlayer.getMaxAttackedBy())
+ " creatures can attack "
+ defendingPlayer.getLogName());
}
return false;
}
@ -1308,7 +1382,7 @@ public class Combat implements Serializable, Copyable<Combat> {
* @param playerId
* @param game
* @param solveBanding check whether also add creatures banded with
* attackerId
* attackerId
*/
public void addBlockingGroup(UUID blockerId, UUID attackerId, UUID playerId, Game game, boolean solveBanding) {
Permanent blocker = game.getPermanent(blockerId);

View file

@ -5,10 +5,12 @@ import java.io.Serializable;
import java.util.*;
import java.util.stream.Stream;
import mage.abilities.Ability;
import mage.abilities.common.ControllerAssignCombatDamageToBlockersAbility;
import mage.abilities.common.ControllerDivideCombatDamageAbility;
import mage.abilities.common.DamageAsThoughNotBlockedAbility;
import mage.abilities.keyword.BandingAbility;
import mage.abilities.keyword.BandsWithOtherAbility;
import mage.abilities.keyword.CantBlockAloneAbility;
import mage.abilities.keyword.DeathtouchAbility;
import mage.abilities.keyword.DoubleStrikeAbility;
@ -110,6 +112,56 @@ public class CombatGroup implements Serializable, Copyable<CombatGroup> {
return perm.getAbilities().containsKey(BandingAbility.getInstance().getId());
}
private boolean appliesBandsWithOther(List<UUID> creatureIds, Game game) {
for (UUID creatureId : creatureIds) {
Permanent perm = game.getPermanent(creatureId);
if (perm != null && perm.getBandedCards() != null) {
for (Ability ab : perm.getAbilities()) {
if (ab.getClass().equals(BandsWithOtherAbility.class)) {
BandsWithOtherAbility ability = (BandsWithOtherAbility) ab;
if (ability.getSubtype() != null) {
if (perm.hasSubtype(ability.getSubtype(), game)) {
for (UUID bandedId : creatureIds) {
if (!bandedId.equals(creatureId)) {
Permanent banded = game.getPermanent(bandedId);
if (banded != null && banded.hasSubtype(ability.getSubtype(), game)) {
return true;
}
}
}
}
}
if (ability.getSupertype() != null) {
if (perm.getSuperType().contains(ability.getSupertype())) {
for (UUID bandedId : creatureIds) {
if (!bandedId.equals(creatureId)) {
Permanent banded = game.getPermanent(bandedId);
if (banded != null && banded.getSuperType().contains(ability.getSupertype())) {
return true;
}
}
}
}
}
if (ability.getName() != null) {
if (perm.getName().equals(ability.getName())) {
for (UUID bandedId : creatureIds) {
if (!bandedId.equals(creatureId)) {
Permanent banded = game.getPermanent(bandedId);
if (banded != null && banded.getName().equals(ability.getName())) {
return true;
}
}
}
}
}
}
}
}
}
return false;
}
public void assignDamageToBlockers(boolean first, Game game) {
if (!attackers.isEmpty() && (!first || hasFirstOrDoubleStrike(game))) {
Permanent attacker = game.getPermanent(attackers.get(0));
@ -837,6 +889,9 @@ public class CombatGroup implements Serializable, Copyable<CombatGroup> {
}
}
}
if (appliesBandsWithOther(attackers, game)) { // 702.21k - both a [quality] creature with bands with other [quality] and another [quality] creature (...)
return true;
}
return false;
}
@ -855,6 +910,9 @@ public class CombatGroup implements Serializable, Copyable<CombatGroup> {
}
}
}
if (appliesBandsWithOther(blockers, game)) { // 702.21j - both a [quality] creature with bands with other [quality] and another [quality] creature (...)
return true;
}
for (Permanent defensiveFormation : game.getBattlefield().getAllActivePermanents(defendingPlayerId)) {
if (defensiveFormation.getAbilities().containsKey(ControllerAssignCombatDamageToBlockersAbility.getInstance().getId())) {
return true;

View file

@ -3,17 +3,16 @@ package mage.game.command;
import java.util.UUID;
import mage.MageObject;
import mage.game.Controllable;
/**
*
* @author Viserion, nantuko
*/
public interface CommandObject extends MageObject {
public interface CommandObject extends MageObject, Controllable {
UUID getSourceId();
UUID getControllerId();
void assignNewId();
MageObject getSourceObject();

View file

@ -1,4 +1,3 @@
package mage.game.command;
import java.util.EnumSet;

View file

@ -1,7 +1,5 @@
package mage.game.command;
import static java.lang.Math.log;
import java.lang.reflect.Constructor;
import java.util.EnumSet;
import java.util.List;
@ -289,7 +287,7 @@ public class Plane implements CommandObject {
if (plane instanceof Plane) {
return (Plane) plane;
}
} catch (Exception ex) {
} catch (Exception ex) {
}
return null;
}

View file

@ -3,11 +3,11 @@ package mage.game.command.emblems;
import mage.abilities.common.DealsDamageToAPlayerAllTriggeredAbility;
import mage.abilities.effects.common.LoseGameTargetPlayerEffect;
import mage.constants.SetTargetPointer;
import mage.constants.Zone;
import mage.filter.StaticFilters;
import mage.game.command.Emblem;
/**
*
* @author TheElk801
*/
public final class VraskaGolgariQueenEmblem extends Emblem {
@ -17,9 +17,9 @@ public final class VraskaGolgariQueenEmblem extends Emblem {
this.setName("Emblem Vraska");
this.setExpansionSetCodeForImage("GRN");
this.getAbilities().add(new DealsDamageToAPlayerAllTriggeredAbility(
new LoseGameTargetPlayerEffect(),
Zone.COMMAND, new LoseGameTargetPlayerEffect(),
StaticFilters.FILTER_CONTROLLED_A_CREATURE,
false, SetTargetPointer.PLAYER, true
false, SetTargetPointer.NONE, true, true
));
}
}

View file

@ -147,7 +147,8 @@ public abstract class DraftImpl implements Draft {
@Override
public void autoPick(UUID playerId) {
this.addPick(playerId, players.get(playerId).getBooster().get(0).getId(), null);
List<Card> booster = players.get(playerId).getBooster();
this.addPick(playerId, booster.get(booster.size()-1).getId(), null);
}
protected void passLeft() {

View file

@ -1,18 +1,17 @@
package mage.game.permanent.token;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.MageInt;
import mage.abilities.keyword.VigilanceAbility;
import mage.constants.CardType;
import mage.constants.SubType;
/**
*
* @author TheElk801
*/
public final class ElfKnightToken extends TokenImpl {
public ElfKnightToken() {
super("Knight Ally", "2/2 green and white Elf Knight creature token with vigilance");
super("Elf Knight", "2/2 green and white Elf Knight creature token with vigilance");
this.setExpansionSetCodeForImage("GRN");
cardType.add(CardType.CREATURE);
color.setGreen(true);

View file

@ -0,0 +1,29 @@
package mage.game.permanent.token;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.MageInt;
/**
*
* @author L_J
*/
public final class ExpansionSymbolToken extends TokenImpl {
public ExpansionSymbolToken() {
super("Expansion-Symbol", "1/1 colorless Expansion-Symbol creature token");
cardType.add(CardType.CREATURE);
subtype.add(SubType.EXPANSION_SYMBOL);
power = new MageInt(1);
toughness = new MageInt(1);
}
public ExpansionSymbolToken(final ExpansionSymbolToken token) {
super(token);
}
public ExpansionSymbolToken copy() {
return new ExpansionSymbolToken(this);
}
}

View file

@ -20,7 +20,7 @@ public final class GoblinToken extends TokenImpl {
static {
tokenImageSets.addAll(Arrays.asList("10E", "ALA", "SOM", "M10", "NPH", "M13", "RTR",
"MMA", "M15", "C14", "KTK", "EVG", "DTK", "ORI", "DDG", "DDN", "DD3EVG", "MM2",
"MM3", "EMA", "C16", "DOM"));
"MM3", "EMA", "C16", "DOM", "ANA"));
}
public GoblinToken(boolean withHaste) {

View file

@ -10,7 +10,6 @@ import mage.constants.SubType;
import mage.target.TargetPermanent;
/**
*
* @author TheElk801
*/
public final class MaskToken extends TokenImpl {
@ -18,8 +17,8 @@ public final class MaskToken extends TokenImpl {
public MaskToken() {
super(
"Mask", "white Aura enchantment token named Mask "
+ "attached to another target permanent. "
+ "The token has enchant permanent and totem armor."
+ "attached to another target permanent. "
+ "The token has enchant permanent and totem armor."
);
cardType.add(CardType.ENCHANTMENT);
color.setWhite(true);
@ -31,6 +30,7 @@ public final class MaskToken extends TokenImpl {
ability.addEffect(new AttachEffect(Outcome.BoostCreature));
this.addAbility(ability);
// Totem armor
this.addAbility(new TotemArmorAbility());
}

View file

@ -0,0 +1,30 @@
package mage.game.permanent.token;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.MageInt;
/**
*
* @author L_J
*/
public final class RabidSheepToken extends TokenImpl {
public RabidSheepToken() {
super("Rabid Sheep", "2/2 green Sheep creature token named Rabid Sheep");
cardType.add(CardType.CREATURE);
subtype.add(SubType.SHEEP);
color.setGreen(true);
power = new MageInt(2);
toughness = new MageInt(2);
}
public RabidSheepToken(final RabidSheepToken token) {
super(token);
}
public RabidSheepToken copy() {
return new RabidSheepToken(this);
}
}

View file

@ -17,7 +17,7 @@ public final class SpiritWhiteToken extends TokenImpl {
final static private List<String> tokenImageSets = new ArrayList<>();
static {
tokenImageSets.addAll(Arrays.asList("AVR", "C14", "CNS", "DDC", "DDK", "FRF", "ISD", "KTK", "M15", "MM2", "SHM", "SOI", "EMA", "C16", "MM3", "CMA", "E01"));
tokenImageSets.addAll(Arrays.asList("AVR", "C14", "CNS", "DDC", "DDK", "FRF", "ISD", "KTK", "M15", "MM2", "SHM", "SOI", "EMA", "C16", "MM3", "CMA", "E01", "ANA"));
}
public SpiritWhiteToken() {

View file

@ -0,0 +1,30 @@
package mage.game.permanent.token;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.MageInt;
/**
*
* @author L_J
*/
public final class UktabiKongApeToken extends TokenImpl {
public UktabiKongApeToken() {
super("Ape", "1/1 green Ape creature token");
cardType.add(CardType.CREATURE);
subtype.add(SubType.APE);
color.setGreen(true);
power = new MageInt(1);
toughness = new MageInt(1);
}
public UktabiKongApeToken(final UktabiKongApeToken token) {
super(token);
}
public UktabiKongApeToken copy() {
return new UktabiKongApeToken(this);
}
}

View file

@ -0,0 +1,32 @@
package mage.game.permanent.token;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.MageInt;
import mage.abilities.keyword.BandsWithOtherAbility;
/**
*
* @author L_J
*/
public final class WolvesOfTheHuntToken extends TokenImpl {
public WolvesOfTheHuntToken() {
super("Wolves of the Hunt", "1/1 green Wolf creature token named Wolves of the Hunt");
cardType.add(CardType.CREATURE);
subtype.add(SubType.WOLF);
color.setGreen(true);
power = new MageInt(1);
toughness = new MageInt(1);
this.addAbility(new BandsWithOtherAbility("Wolves of the Hunt"));
}
public WolvesOfTheHuntToken(final WolvesOfTheHuntToken token) {
super(token);
}
public WolvesOfTheHuntToken copy() {
return new WolvesOfTheHuntToken(this);
}
}

View file

@ -518,14 +518,19 @@ public class StackAbility extends StackObjImpl implements Ability {
return this.ability.getSourcePermanentIfItStillExists(game);
}
@Override
public void setSourceObjectZoneChangeCounter(int zoneChangeCounter) {
ability.setSourceObjectZoneChangeCounter(zoneChangeCounter);
}
@Override
public int getSourceObjectZoneChangeCounter() {
return ability.getSourceObjectZoneChangeCounter();
}
@Override
public void setSourceObject(MageObject sourceObject, Game game) {
throw new UnsupportedOperationException("Not supported.");
public Permanent getSourcePermanentOrLKI(Game game) {
return ability.getSourcePermanentOrLKI(game);
}
@Override

View file

@ -1,5 +1,7 @@
package mage.players;
import java.io.Serializable;
import java.util.*;
import mage.MageItem;
import mage.MageObject;
import mage.MageObjectReference;
@ -37,9 +39,6 @@ import mage.target.TargetCard;
import mage.target.common.TargetCardInLibrary;
import mage.util.Copyable;
import java.io.Serializable;
import java.util.*;
/**
* @author BetaSteward_at_googlemail.com
*/
@ -74,7 +73,7 @@ public interface Player extends MageItem, Copyable<Player> {
void setLife(int life, Game game, UUID sourceId);
/**
* @param amount amount of life loss
* @param amount amount of life loss
* @param game
* @param atCombat was the source combat damage
* @return
@ -345,7 +344,7 @@ public interface Player extends MageItem, Copyable<Player> {
* @param target
* @param game
* @param targetPlayerId player whose library will be searched
* @param triggerEvents whether searching will trigger any game events
* @param triggerEvents whether searching will trigger any game events
* @return true if search was successful
*/
boolean searchLibrary(TargetCardInLibrary target, Game game, UUID targetPlayerId, boolean triggerEvents);
@ -355,22 +354,23 @@ public interface Player extends MageItem, Copyable<Player> {
/**
* Plays a card if possible
*
* @param card the card that can be cast
* @param card the card that can be cast
* @param game
* @param noMana if it's a spell i can be cast without paying mana
* @param noMana if it's a spell i can be cast without paying mana
* @param ignoreTiming if it's cast during the resolution of another spell
* no sorcery or play land timing restriction are checked. For a land it has
* to be the turn of the player playing that card.
* no sorcery or play land timing restriction are checked. For a land it has
* to be the turn of the player playing that card.
* @param reference mage object that allows to play the card
* @return
*/
boolean playCard(Card card, Game game, boolean noMana, boolean ignoreTiming, MageObjectReference reference);
/**
* @param card the land card to play
* @param card the land card to play
* @param game
* @param ignoreTiming false - it won't be checked if the stack is empty and
* you are able to play a Sorcery. It's still checked, if you are able to
* play a land concerning the numner of lands you already played.
* you are able to play a Sorcery. It's still checked, if you are able to
* play a land concerning the number of lands you already played.
* @return
*/
boolean playLand(Card card, Game game, boolean ignoreTiming);
@ -516,11 +516,11 @@ public interface Player extends MageItem, Copyable<Player> {
/**
* Moves the cards from cards to the bottom of the players library.
*
* @param cards - list of cards that have to be moved
* @param game - game
* @param cards - list of cards that have to be moved
* @param game - game
* @param anyOrder - true if player can determine the order of the cards
* else random order
* @param source - source ability
* else random order
* @param source - source ability
* @return
*/
boolean putCardsOnBottomOfLibrary(Cards cards, Game game, Ability source, boolean anyOrder);
@ -541,10 +541,10 @@ public interface Player extends MageItem, Copyable<Player> {
/**
* Moves the cards from cards to the top of players library.
*
* @param cards - list of cards that have to be moved
* @param game - game
* @param cards - list of cards that have to be moved
* @param game - game
* @param anyOrder - true if player can determine the order of the cards
* @param source - source ability
* @param source - source ability
* @return
*/
boolean putCardsOnTopOfLibrary(Cards cards, Game game, Ability source, boolean anyOrder);
@ -570,8 +570,8 @@ public interface Player extends MageItem, Copyable<Player> {
/**
* Choose the order in which blockers get damage assigned to
*
* @param blockers list of blockers where to choose the next one from
* @param combatGroup the concerning combat group
* @param blockers list of blockers where to choose the next one from
* @param combatGroup the concerning combat group
* @param blockerOrder the already set order of blockers
* @param game
* @return blocker next to add to the blocker order
@ -710,11 +710,11 @@ public interface Player extends MageItem, Copyable<Player> {
* @param toZone
* @param source
* @param game
* @param tapped the cards are tapped on the battlefield
* @param faceDown the cards are face down in the to zone
* @param byOwner the card is moved (or put onto battlefield) by the owner
* of the card and if target zone is battlefield controls the permanent
* (instead of the controller of the source)
* @param tapped the cards are tapped on the battlefield
* @param faceDown the cards are face down in the to zone
* @param byOwner the card is moved (or put onto battlefield) by the owner
* of the card and if target zone is battlefield controls the permanent
* (instead of the controller of the source)
* @param appliedEffects
* @return
*/
@ -750,7 +750,7 @@ public interface Player extends MageItem, Copyable<Player> {
* list of applied effects is not saved
*
* @param card
* @param exileId exile zone id (optional)
* @param exileId exile zone id (optional)
* @param exileName name of exile zone (optional)
* @param sourceId
* @param game
@ -792,7 +792,7 @@ public interface Player extends MageItem, Copyable<Player> {
* @param sourceId
* @param game
* @param fromZone if null, this info isn't postet
* @param toTop to the top of the library else to the bottom
* @param toTop to the top of the library else to the bottom
* @param withName show the card name in the log
* @return
*/
@ -817,10 +817,10 @@ public interface Player extends MageItem, Copyable<Player> {
* without mana (null) or the mana set to manaCosts instead of its normal
* mana costs.
*
* @param sourceId the source that can be cast without mana
* @param sourceId the source that can be cast without mana
* @param manaCosts alternate ManaCost, null if it can be cast without mana
* cost
* @param costs alternate other costs you need to pay
* cost
* @param costs alternate other costs you need to pay
*/
void setCastSourceIdWithAlternateMana(UUID sourceId, ManaCosts<ManaCost> manaCosts, Costs<Cost> costs);

View file

@ -1,5 +1,9 @@
package mage.players;
import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.Map.Entry;
import mage.ConditionalMana;
import mage.MageObject;
import mage.MageObjectReference;
@ -68,11 +72,6 @@ import mage.util.GameLog;
import mage.util.RandomUtil;
import org.apache.log4j.Logger;
import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.Map.Entry;
public abstract class PlayerImpl implements Player, Serializable {
private static final Logger logger = Logger.getLogger(PlayerImpl.class);
@ -1042,15 +1041,15 @@ public abstract class PlayerImpl implements Player, Serializable {
}
@Override
public boolean cast(SpellAbility ability, Game game, boolean noMana, MageObjectReference permittingObject) {
if (game == null || ability == null) {
public boolean cast(SpellAbility originalAbility, Game game, boolean noMana, MageObjectReference permittingObject) {
if (game == null || originalAbility == null) {
return false;
}
// Use ability copy to avoid problems with targets and costs on recast (issue https://github.com/magefree/mage/issues/5189).
ability = ability.copy();
SpellAbility ability = originalAbility.copy();
ability.setControllerId(getId());
ability.setSourceObjectZoneChangeCounter(game.getState().getZoneChangeCounter(ability.getSourceId()));
if (ability.getSpellAbilityType() != SpellAbilityType.BASE) {
ability = chooseSpellAbilityForCast(ability, game, noMana);
if (ability == null) {
@ -1074,6 +1073,8 @@ public abstract class PlayerImpl implements Player, Serializable {
logger.error("Got no spell from stack. ability: " + ability.getRule());
return false;
}
// Update the zcc to the stack
ability.setSourceObjectZoneChangeCounter(game.getState().getZoneChangeCounter(ability.getSourceId()));
// some effects set sourceId to cast without paying mana costs or other costs
if (ability.getSourceId().equals(getCastSourceIdWithAlternateMana())) {
Ability spellAbility = spell.getSpellAbility();
@ -1144,8 +1145,14 @@ public abstract class PlayerImpl implements Player, Serializable {
}
//20091005 - 114.2a
ActivationStatus activationStatus = playLandAbility.canActivate(this.playerId, game);
if (!ignoreTiming && !activationStatus.canActivate()) {
return false;
if (ignoreTiming) {
if (!canPlayLand()) {
return false; // ignore timing does not mean that more lands than normal can be played
}
} else {
if (!activationStatus.canActivate()) {
return false;
}
}
//20091005 - 305.1
@ -2365,7 +2372,9 @@ public abstract class PlayerImpl implements Player, Serializable {
setStoredBookmark(game.bookmarkState()); // makes it possible to UNDO a declared attacker with costs from e.g. Propaganda
}
Permanent attacker = game.getPermanent(attackerId);
if (attacker != null && attacker.canAttack(defenderId, game) && attacker.isControlledBy(playerId)) {
if (attacker != null
&& attacker.canAttack(defenderId, game)
&& attacker.isControlledBy(playerId)) {
if (!game.getCombat().declareAttacker(attackerId, defenderId, playerId, game)) {
game.undo(playerId);
}
@ -2464,13 +2473,14 @@ public abstract class PlayerImpl implements Player, Serializable {
for (UUID targetId : newTarget.getTargets()) {
target.add(targetId, game);
}
if (triggerEvents) {
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.LIBRARY_SEARCHED, targetPlayerId, playerId));
}
} else if (targetPlayerId.equals(playerId) && handleLibraryCastableCards(library, game, targetPlayerId)) { // for handling Panglacial Wurm
newTarget.clearChosen();
continue;
}
if (triggerEvents) {
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.LIBRARY_SEARCHED, targetPlayerId, playerId));
}
break;
} while (true);
return true;
@ -2563,7 +2573,7 @@ public abstract class PlayerImpl implements Player, Serializable {
/**
* @param game
* @param appliedEffects
* @param numSides Number of sides the dice has
* @param numSides Number of sides the dice has
* @return the number that the player rolled
*/
@Override
@ -2597,10 +2607,10 @@ public abstract class PlayerImpl implements Player, Serializable {
/**
* @param game
* @param appliedEffects
* @param numberChaosSides The number of chaos sides the planar die
* currently has (normally 1 but can be 5)
* @param numberChaosSides The number of chaos sides the planar die
* currently has (normally 1 but can be 5)
* @param numberPlanarSides The number of chaos sides the planar die
* currently has (normally 1)
* currently has (normally 1)
* @return the outcome that the player rolled. Either ChaosRoll, PlanarRoll
* or NilRoll
*/
@ -2757,7 +2767,7 @@ public abstract class PlayerImpl implements Player, Serializable {
/**
* @param ability
* @param available if null, it won't be checked if enough mana is available
* @param available if null, it won't be checked if enough mana is available
* @param sourceObject
* @param game
* @return
@ -2927,7 +2937,7 @@ public abstract class PlayerImpl implements Player, Serializable {
}
private void getPlayableFromGraveyardCard(Game game, Card card, Abilities<Ability> candidateAbilities, ManaOptions availableMana, List<Ability> output) {
MageObjectReference permittingObject = game.getContinuousEffects().asThough(card.getId(), AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, null, this.getId(), game);
MageObjectReference permittingObject = game.getContinuousEffects().asThough(card.getId(), AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, card.getSpellAbility(), this.getId(), game);
for (ActivatedAbility ability : candidateAbilities.getActivatedAbilities(Zone.ALL)) {
boolean possible = false;
if (ability.getZone().match(Zone.GRAVEYARD)) {
@ -3033,7 +3043,7 @@ public abstract class PlayerImpl implements Player, Serializable {
if (player != null) {
if (/*player.isTopCardRevealed() &&*/player.getLibrary().hasCards()) {
Card card = player.getLibrary().getFromTop(game);
if (null != game.getContinuousEffects().asThough(card.getId(), AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, null, getId(), game)) {
if (null != game.getContinuousEffects().asThough(card.getId(), AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, card.getSpellAbility(), getId(), game)) {
for (ActivatedAbility ability : card.getAbilities().getActivatedAbilities(Zone.HAND)) {
if (ability instanceof SpellAbility || ability instanceof PlayLandAbility) {
playable.add(ability);
@ -3309,7 +3319,7 @@ public abstract class PlayerImpl implements Player, Serializable {
@Override
public boolean canPaySacrificeCost(Permanent permanent, UUID sourceId,
UUID controllerId, Game game
UUID controllerId, Game game
) {
return sacrificeCostFilter == null || !sacrificeCostFilter.match(permanent, sourceId, controllerId, game);
}
@ -3457,8 +3467,8 @@ public abstract class PlayerImpl implements Player, Serializable {
@Override
public boolean moveCards(Card card, Zone toZone,
Ability source, Game game,
boolean tapped, boolean faceDown, boolean byOwner, List<UUID> appliedEffects
Ability source, Game game,
boolean tapped, boolean faceDown, boolean byOwner, List<UUID> appliedEffects
) {
Set<Card> cardList = new HashSet<>();
if (card != null) {
@ -3469,22 +3479,22 @@ public abstract class PlayerImpl implements Player, Serializable {
@Override
public boolean moveCards(Cards cards, Zone toZone,
Ability source, Game game
Ability source, Game game
) {
return moveCards(cards.getCards(game), toZone, source, game);
}
@Override
public boolean moveCards(Set<Card> cards, Zone toZone,
Ability source, Game game
Ability source, Game game
) {
return moveCards(cards, toZone, source, game, false, false, false, null);
}
@Override
public boolean moveCards(Set<Card> cards, Zone toZone,
Ability source, Game game,
boolean tapped, boolean faceDown, boolean byOwner, List<UUID> appliedEffects
Ability source, Game game,
boolean tapped, boolean faceDown, boolean byOwner, List<UUID> appliedEffects
) {
if (cards.isEmpty()) {
return true;
@ -3570,8 +3580,8 @@ public abstract class PlayerImpl implements Player, Serializable {
@Override
public boolean moveCardsToExile(Card card, Ability source,
Game game, boolean withName, UUID exileId,
String exileZoneName
Game game, boolean withName, UUID exileId,
String exileZoneName
) {
Set<Card> cards = new HashSet<>();
cards.add(card);
@ -3580,8 +3590,8 @@ public abstract class PlayerImpl implements Player, Serializable {
@Override
public boolean moveCardsToExile(Set<Card> cards, Ability source,
Game game, boolean withName, UUID exileId,
String exileZoneName
Game game, boolean withName, UUID exileId,
String exileZoneName
) {
if (cards.isEmpty()) {
return true;
@ -3596,14 +3606,14 @@ public abstract class PlayerImpl implements Player, Serializable {
@Override
public boolean moveCardToHandWithInfo(Card card, UUID sourceId,
Game game
Game game
) {
return this.moveCardToHandWithInfo(card, sourceId, game, true);
}
@Override
public boolean moveCardToHandWithInfo(Card card, UUID sourceId,
Game game, boolean withName
Game game, boolean withName
) {
boolean result = false;
Zone fromZone = game.getState().getZone(card.getId());
@ -3628,7 +3638,7 @@ public abstract class PlayerImpl implements Player, Serializable {
@Override
public Set<Card> moveCardsToGraveyardWithInfo(Set<Card> allCards, Ability source,
Game game, Zone fromZone
Game game, Zone fromZone
) {
UUID sourceId = source == null ? null : source.getSourceId();
Set<Card> movedCards = new LinkedHashSet<>();
@ -3636,7 +3646,7 @@ public abstract class PlayerImpl implements Player, Serializable {
// identify cards from one owner
Cards cards = new CardsImpl();
UUID ownerId = null;
for (Iterator<Card> it = allCards.iterator(); it.hasNext(); ) {
for (Iterator<Card> it = allCards.iterator(); it.hasNext();) {
Card card = it.next();
if (cards.isEmpty()) {
ownerId = card.getOwnerId();
@ -3697,7 +3707,7 @@ public abstract class PlayerImpl implements Player, Serializable {
@Override
public boolean moveCardToGraveyardWithInfo(Card card, UUID sourceId,
Game game, Zone fromZone
Game game, Zone fromZone
) {
if (card == null) {
return false;
@ -3726,8 +3736,8 @@ public abstract class PlayerImpl implements Player, Serializable {
@Override
public boolean moveCardToLibraryWithInfo(Card card, UUID sourceId,
Game game, Zone fromZone,
boolean toTop, boolean withName
Game game, Zone fromZone,
boolean toTop, boolean withName
) {
if (card == null) {
return false;
@ -3761,7 +3771,7 @@ public abstract class PlayerImpl implements Player, Serializable {
@Override
public boolean moveCardToExileWithInfo(Card card, UUID exileId, String exileName, UUID sourceId,
Game game, Zone fromZone, boolean withName) {
Game game, Zone fromZone, boolean withName) {
if (card == null) {
return false;
}

View file

@ -1,16 +1,14 @@
package mage.target;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import mage.MageObject;
import mage.abilities.Ability;
import mage.filter.FilterPlayer;
import mage.game.Game;
import mage.players.Player;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
/**
*
* @author BetaSteward_at_googlemail.com

View file

@ -68,7 +68,8 @@ public class TargetDefender extends TargetImpl {
}
}
for (Permanent permanent : game.getBattlefield().getActivePermanents(StaticFilters.FILTER_PERMANENT_PLANESWALKER, sourceControllerId, game)) {
if ((notTarget || permanent.canBeTargetedBy(targetSource, sourceControllerId, game))
if ((notTarget
|| permanent.canBeTargetedBy(targetSource, sourceControllerId, game))
&& filter.match(permanent, game)) {
count++;
if (count >= this.minNumberOfTargets) {
@ -84,7 +85,8 @@ public class TargetDefender extends TargetImpl {
int count = 0;
for (UUID playerId : game.getState().getPlayersInRange(sourceControllerId, game)) {
Player player = game.getPlayer(playerId);
if (player != null && filter.match(player, game)) {
if (player != null
&& filter.match(player, game)) {
count++;
if (count >= this.minNumberOfTargets) {
return true;
@ -109,13 +111,15 @@ public class TargetDefender extends TargetImpl {
for (UUID playerId : game.getState().getPlayersInRange(sourceControllerId, game)) {
Player player = game.getPlayer(playerId);
if (player != null
&& (notTarget || player.canBeTargetedBy(targetSource, sourceControllerId, game))
&& (notTarget
|| player.canBeTargetedBy(targetSource, sourceControllerId, game))
&& filter.match(player, game)) {
possibleTargets.add(playerId);
}
}
for (Permanent permanent : game.getBattlefield().getActivePermanents(StaticFilters.FILTER_PERMANENT_PLANESWALKER, sourceControllerId, game)) {
if ((notTarget || permanent.canBeTargetedBy(targetSource, sourceControllerId, game))
if ((notTarget
|| permanent.canBeTargetedBy(targetSource, sourceControllerId, game))
&& filter.match(permanent, game)) {
possibleTargets.add(permanent.getId());
}
@ -128,7 +132,8 @@ public class TargetDefender extends TargetImpl {
Set<UUID> possibleTargets = new HashSet<>();
for (UUID playerId : game.getState().getPlayersInRange(sourceControllerId, game)) {
Player player = game.getPlayer(playerId);
if (player != null && filter.match(player, game)) {
if (player != null
&& filter.match(player, game)) {
possibleTargets.add(playerId);
}
}
@ -162,7 +167,8 @@ public class TargetDefender extends TargetImpl {
return filter.match(player, game);
}
Permanent permanent = game.getPermanent(id);
return permanent != null && filter.match(permanent, game);
return permanent != null
&& filter.match(permanent, game);
}
@Override
@ -170,7 +176,8 @@ public class TargetDefender extends TargetImpl {
Player player = game.getPlayer(id);
MageObject targetSource = game.getObject(attackerId);
if (player != null) {
return (notTarget || player.canBeTargetedBy(targetSource, (source == null ? null : source.getControllerId()), game))
return (notTarget
|| player.canBeTargetedBy(targetSource, (source == null ? null : source.getControllerId()), game))
&& filter.match(player, game);
}
Permanent permanent = game.getPermanent(id); // planeswalker
@ -180,7 +187,8 @@ public class TargetDefender extends TargetImpl {
if (source != null) {
controllerId = source.getControllerId();
}
return (notTarget || permanent.canBeTargetedBy(targetSource, controllerId, game))
return (notTarget
|| permanent.canBeTargetedBy(targetSource, controllerId, game))
&& filter.match(permanent, game);
}
return false;

View file

@ -21,6 +21,12 @@ public class FixedTarget implements TargetPointer {
this.initialized = false;
}
public FixedTarget(Card card, Game game) {
this.targetId = card.getId();
this.zoneChangeCounter = card.getZoneChangeCounter(game);
this.initialized = true;
}
public FixedTarget(Permanent permanent, Game game) {
this.targetId = permanent.getId();
this.zoneChangeCounter = permanent.getZoneChangeCounter(game);

View file

@ -362,7 +362,7 @@ public final class CardUtil {
/**
* Parse card number as int (support base [123] and alternative numbers
* [123b]).
* [123b], [U123]).
*
* @param cardNumber origin card number
* @return int
@ -373,10 +373,15 @@ public final class CardUtil {
throw new IllegalArgumentException("Card number is empty.");
}
if (Character.isDigit(cardNumber.charAt(cardNumber.length() - 1))) {
return Integer.parseInt(cardNumber);
} else {
if (!Character.isDigit(cardNumber.charAt(0))) {
// U123
return Integer.parseInt(cardNumber.substring(1, cardNumber.length()));
} else if (!Character.isDigit(cardNumber.charAt(cardNumber.length() - 1))) {
// 123b
return Integer.parseInt(cardNumber.substring(0, cardNumber.length() - 1));
} else {
// 123
return Integer.parseInt(cardNumber);
}
}

View file

@ -33,8 +33,8 @@ public final class ClassScanner {
if(classLoader == null) classLoader = Thread.currentThread().getContextClassLoader();
assert classLoader != null;
HashMap<String, String> dirs = new HashMap<>();
TreeSet<String> jars = new TreeSet<>();
Map<String, String> dirs = new HashMap<>();
Set<String> jars = new TreeSet<>();
for (String packageName : packages) {
String path = packageName.replace('.', '/');
Enumeration<URL> resources = classLoader.getResources(path);
@ -51,8 +51,8 @@ public final class ClassScanner {
}
}
for (String filePath : dirs.keySet()) {
cards.addAll(findClasses(classLoader, new File(filePath), dirs.get(filePath), type));
for (Map.Entry<String, String> dir : dirs.entrySet()) {
cards.addAll(findClasses(classLoader, new File(dir.getKey()), dir.getValue(), type));
}
for (String filePath : jars) {
@ -66,7 +66,7 @@ public final class ClassScanner {
private static List<Class> findClasses(ClassLoader classLoader, File directory, String packageName, Class<?> type) {
List<Class> cards = new ArrayList<>();
if (!directory.exists()) return cards;
if (directory == null || !directory.exists()) return cards;
for (File file : directory.listFiles()) {
if (file.getName().endsWith(".class")) {

View file

@ -0,0 +1,91 @@
package mage.watchers.common;
import mage.constants.WatcherScope;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.players.PlayerList;
import mage.watchers.Watcher;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* @author JayDi85
*/
public class PlayersAttackedThisTurnWatcher extends Watcher {
// how many players or opponents each player attacked this turn
private final Map<UUID, PlayerList> playersAttackedThisTurn = new HashMap<>();
private final Map<UUID, PlayerList> opponentsAttackedThisTurn = new HashMap<>();
public PlayersAttackedThisTurnWatcher() {
super(PlayersAttackedThisTurnWatcher.class.getSimpleName(), WatcherScope.GAME);
}
public PlayersAttackedThisTurnWatcher(final PlayersAttackedThisTurnWatcher watcher) {
super(watcher);
for (Map.Entry<UUID, PlayerList> entry : watcher.playersAttackedThisTurn.entrySet()) {
this.playersAttackedThisTurn.put(entry.getKey(), entry.getValue());
}
for (Map.Entry<UUID, PlayerList> entry : watcher.opponentsAttackedThisTurn.entrySet()) {
this.opponentsAttackedThisTurn.put(entry.getKey(), entry.getValue());
}
}
@Override
public PlayersAttackedThisTurnWatcher copy() {
return new PlayersAttackedThisTurnWatcher(this);
}
@Override
public void watch(GameEvent event, Game game) {
if (event.getType() == GameEvent.EventType.BEGINNING_PHASE_PRE) {
playersAttackedThisTurn.clear();
opponentsAttackedThisTurn.clear();
}
if (event.getType() == GameEvent.EventType.ATTACKER_DECLARED) {
// players
PlayerList playersAttacked = playersAttackedThisTurn.get(event.getPlayerId());
if (playersAttacked == null) {
playersAttacked = new PlayerList();
}
UUID playerDefender = game.getCombat().getDefendingPlayerId(event.getSourceId(), game);
if (playerDefender != null) {
playersAttacked.add(playerDefender);
}
playersAttackedThisTurn.put(event.getPlayerId(), playersAttacked);
// opponents
PlayerList opponentsAttacked = opponentsAttackedThisTurn.get(event.getPlayerId());
if (opponentsAttacked == null) {
opponentsAttacked = new PlayerList();
}
UUID opponentDefender = game.getCombat().getDefendingPlayerId(event.getSourceId(), game);
if (opponentDefender != null && game.getOpponents(event.getPlayerId()).contains(opponentDefender)) {
opponentsAttacked.add(opponentDefender);
}
opponentsAttackedThisTurn.put(event.getPlayerId(), opponentsAttacked);
}
}
public int getAttackedPlayersCount(UUID playerID) {
PlayerList defendersList = playersAttackedThisTurn.getOrDefault(playerID, null);
if (defendersList != null) {
return defendersList.size();
}
return 0;
}
public int getAttackedOpponentsCount(UUID playerID) {
PlayerList defendersList = opponentsAttackedThisTurn.getOrDefault(playerID, null);
if (defendersList != null) {
return defendersList.size();
}
return 0;
}
}