Merge branch 'master' into copy_constructor_watchers

This commit is contained in:
Oleg Agafonov 2019-05-18 09:13:42 +04:00
commit 85c6528d2d
724 changed files with 23423 additions and 5250 deletions

View file

@ -53,6 +53,10 @@ public class MageObjectReference implements Comparable<MageObjectReference>, Ser
public MageObjectReference(UUID sourceId, Game game) {
this.sourceId = sourceId;
if (sourceId == null) {
throw new IllegalArgumentException("MageObjectReference contains nullable sourceId");
}
MageObject mageObject = game.getObject(sourceId);
if (mageObject != null) {
this.zoneChangeCounter = mageObject.getZoneChangeCounter(game);

View file

@ -1,5 +1,3 @@
package mage.abilities;
import mage.constants.Duration;
@ -48,8 +46,8 @@ public class DelayedTriggeredAbilities extends AbilitiesImpl<DelayedTriggeredAbi
}
}
public void removeEndOfTurnAbilities() {
this.removeIf(ability -> ability.getDuration() == Duration.EndOfTurn);
public void removeEndOfTurnAbilities(Game game) {
this.removeIf(ability -> ability.getDuration() == Duration.EndOfTurn); // TODO: add Duration.EndOfYourTurn like effects
}
public void removeEndOfCombatAbilities() {

View file

@ -335,26 +335,31 @@ public class Modes extends LinkedHashMap<UUID, Mode> {
if (this.getMaxModesFilter() != null) {
sb.append("choose one or more. Each mode must target ").append(getMaxModesFilter().getMessage());
} else if (this.getMinModes() == 0 && this.getMaxModes() == 1) {
sb.append("choose up to one ");
sb.append("choose up to one");
} else if (this.getMinModes() == 1 && this.getMaxModes() > 2) {
sb.append("choose one or more ");
sb.append("choose one or more");
} else if (this.getMinModes() == 1 && this.getMaxModes() == 2) {
sb.append("choose one or both ");
sb.append("choose one or both");
} else if (this.getMinModes() == 2 && this.getMaxModes() == 2) {
sb.append("choose two ");
sb.append("choose two");
} else if (this.getMinModes() == 3 && this.getMaxModes() == 3) {
sb.append("choose three ");
sb.append("choose three");
} else if (this.getMinModes() == 4 && this.getMaxModes() == 4) {
sb.append("choose four");
} else {
sb.append("choose one ");
sb.append("choose one");
}
if (isEachModeOnlyOnce()) {
sb.append("that hasn't been chosen ");
sb.append(" that hasn't been chosen");
}
if (isEachModeMoreThanOnce()) {
sb.append(". You may choose the same mode more than once.<br>");
} else {
sb.append("&mdash;<br>");
sb.append(" &mdash;<br>");
}
for (Mode mode : this.values()) {
sb.append("&bull ");
sb.append(mode.getEffects().getTextStartingUpperCase(mode));

View file

@ -1,5 +1,3 @@
package mage.abilities.abilityword;
import mage.abilities.Ability;
@ -21,28 +19,27 @@ import mage.players.Player;
import mage.target.targetpointer.FixedTarget;
/**
*
* @author LevelX2
*/
public class KinshipAbility extends TriggeredAbilityImpl {
public KinshipAbility(Effect kinshipEffect) {
super(Zone.BATTLEFIELD, new KinshipBaseEffect(kinshipEffect), true);
super(Zone.BATTLEFIELD, new KinshipBaseEffect(kinshipEffect), true);
}
public KinshipAbility(final KinshipAbility ability) {
super(ability);
super(ability);
}
public void addKinshipEffect(Effect kinshipEffect) {
for (Effect effect: this.getEffects()) {
for (Effect effect : this.getEffects()) {
if (effect instanceof KinshipBaseEffect) {
((KinshipBaseEffect) effect).addEffect(kinshipEffect);
break;
((KinshipBaseEffect) effect).addEffect(kinshipEffect);
break;
}
}
}
}
@Override
public KinshipAbility copy() {
return new KinshipAbility(this);
@ -62,33 +59,33 @@ public class KinshipAbility extends TriggeredAbilityImpl {
public String getRule() {
return new StringBuilder("<i>Kinship</i> &mdash; At the beginning of your upkeep, ").append(super.getRule()).toString();
}
}
class KinshipBaseEffect extends OneShotEffect {
private final Effects kinshipEffects = new Effects();
public KinshipBaseEffect(Effect kinshipEffect) {
super(kinshipEffect.getOutcome());
this.kinshipEffects.add(kinshipEffect);
this.staticText = "you may look at the top card of your library. If it shares a creature type with {this}, you may reveal it. If you do, ";
}
public KinshipBaseEffect(final KinshipBaseEffect effect) {
super(effect);
this.kinshipEffects.addAll(effect.kinshipEffects);
this.kinshipEffects.addAll(effect.kinshipEffects.copy());
}
public void addEffect(Effect kinshipEffect) {
this.kinshipEffects.add(kinshipEffect);
}
@Override
public KinshipBaseEffect copy() {
return new KinshipBaseEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
@ -100,22 +97,22 @@ class KinshipBaseEffect extends OneShotEffect {
Cards cards = new CardsImpl(card);
controller.lookAtCards(sourcePermanent.getName(), cards, game);
if (sourcePermanent.shareSubtypes(card, game)) {
if (controller.chooseUse(outcome,new StringBuilder("Kinship - Reveal ").append(card.getLogName()).append('?').toString(), source, game)) {
if (controller.chooseUse(outcome, new StringBuilder("Kinship - Reveal ").append(card.getLogName()).append('?').toString(), source, game)) {
controller.revealCards(sourcePermanent.getName(), cards, game);
for (Effect effect: kinshipEffects) {
for (Effect effect : kinshipEffects) {
effect.setTargetPointer(new FixedTarget(card.getId()));
if (effect.getEffectType() == EffectType.ONESHOT) {
effect.apply(game, source);
} else {
if (effect instanceof ContinuousEffect) {
game.addEffect((ContinuousEffect)effect, source);
game.addEffect((ContinuousEffect) effect, source);
} else {
throw new UnsupportedOperationException("This kind of effect is not supported");
}
}
}
}
}
}
}
}
}
return true;
@ -125,7 +122,7 @@ class KinshipBaseEffect extends OneShotEffect {
@Override
public String getText(Mode mode) {
return new StringBuilder(super.getText(mode)).append(kinshipEffects.getText(mode)).toString();
return new StringBuilder(super.getText(mode)).append(kinshipEffects.getText(mode)).toString();
}
}

View file

@ -70,6 +70,16 @@ public class BeginningOfUpkeepTriggeredAbility extends TriggeredAbilityImpl {
}
}
return yours;
case NOT_YOU:
boolean notYours = !event.getPlayerId().equals(this.controllerId);
if (notYours && setTargetPointer) {
if (getTargets().isEmpty()) {
for (Effect effect : this.getEffects()) {
effect.setTargetPointer(new FixedTarget(event.getPlayerId()));
}
}
}
return notYours;
case OPPONENT:
if (game.getPlayer(this.controllerId).hasOpponent(event.getPlayerId(), game)) {
if (setTargetPointer && getTargets().isEmpty()) {

View file

@ -1,7 +1,7 @@
package mage.abilities.common;
import mage.abilities.SpellAbility;
import mage.abilities.costs.CostsImpl;
import mage.cards.Card;
import mage.constants.SpellAbilityType;
import mage.constants.TimingRule;
@ -9,15 +9,21 @@ import mage.constants.Zone;
import mage.game.Game;
/**
*
* @author Plopman
*/
public class CastCommanderAbility extends SpellAbility {
public CastCommanderAbility(Card card) {
super(card.getManaCost(), card.getName(), Zone.COMMAND, SpellAbilityType.BASE);
this.costs = card.getSpellAbility().getCosts().copy();
this.timing = TimingRule.SORCERY;
if (card.getSpellAbility() != null) {
this.getCosts().addAll(card.getSpellAbility().getCosts().copy());
this.getEffects().addAll(card.getSpellAbility().getEffects().copy());
this.getTargets().addAll(card.getSpellAbility().getTargets().copy());
this.timing = card.getSpellAbility().getTiming();
} else {
this.costs = new CostsImpl<>();
this.timing = TimingRule.SORCERY;
}
this.usesStack = true;
this.controllerId = card.getOwnerId();
this.sourceId = card.getId();

View file

@ -1,4 +1,3 @@
package mage.abilities.common;
import mage.abilities.TriggeredAbilityImpl;
@ -45,6 +44,7 @@ public class DealsCombatDamageToAPlayerTriggeredAbility extends TriggeredAbility
this.text = ability.text;
this.setTargetPointer = ability.setTargetPointer;
this.onlyOpponents = ability.onlyOpponents;
this.orPlaneswalker = ability.orPlaneswalker;
}
public DealsCombatDamageToAPlayerTriggeredAbility setOrPlaneswalker(boolean orPlaneswalker) {

View file

@ -0,0 +1,92 @@
package mage.abilities.common;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.OneShotEffect;
import mage.cards.Card;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.ZoneChangeEvent;
import mage.players.Player;
/**
* @author TheElk801
*/
public class GodEternalDiesTriggeredAbility extends TriggeredAbilityImpl {
public GodEternalDiesTriggeredAbility() {
super(Zone.ALL, null, true);
}
private GodEternalDiesTriggeredAbility(GodEternalDiesTriggeredAbility ability) {
super(ability);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
if (event.getType() == GameEvent.EventType.ZONE_CHANGE) {
ZoneChangeEvent zEvent = (ZoneChangeEvent) event;
return zEvent.getFromZone() == Zone.BATTLEFIELD
&& (zEvent.getToZone() == Zone.GRAVEYARD || zEvent.getToZone() == Zone.EXILED);
}
return false;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
ZoneChangeEvent zEvent = (ZoneChangeEvent) event;
if (zEvent.getTargetId().equals(this.getSourceId())) {
this.getEffects().clear();
this.addEffect(new GodEternalEffect(new MageObjectReference(zEvent.getTarget(), game)));
return true;
}
return false;
}
@Override
public GodEternalDiesTriggeredAbility copy() {
return new GodEternalDiesTriggeredAbility(this);
}
@Override
public String getRule() {
return "When {this} dies or is put into exile from the battlefield, " +
"you may put it into its owner's library third from the top.";
}
}
class GodEternalEffect extends OneShotEffect {
private final MageObjectReference mor;
GodEternalEffect(MageObjectReference mor) {
super(Outcome.Benefit);
this.mor = mor;
}
private GodEternalEffect(final GodEternalEffect effect) {
super(effect);
this.mor = effect.mor;
}
@Override
public GodEternalEffect copy() {
return new GodEternalEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player player = game.getPlayer(source.getControllerId());
if (player == null) {
return false;
}
Card card = game.getCard(mor.getSourceId());
if (card.getZoneChangeCounter(game) - 1 != mor.getZoneChangeCounter()) {
return false;
}
return player.putCardOnTopXOfLibrary(card, game, source, 3);
}
}

View file

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

View file

@ -1,9 +1,5 @@
package mage.abilities.costs.common;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.costs.Cost;
import mage.abilities.costs.CostImpl;
@ -18,8 +14,11 @@ import mage.players.Player;
import mage.target.TargetPermanent;
import mage.util.CardUtil;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
/**
*
* @author LevelX
*/
public class RemoveCounterCost extends CostImpl {
@ -102,10 +101,6 @@ public class RemoveCounterCost extends CostImpl {
new StringBuilder("Remove how many counters from ").append(permanent.getIdName()).toString(), game);
}
permanent.removeCounters(counterName, numberOfCountersSelected, game);
if (permanent.getCounters(game).getCount(counterName) == 0) {
// this removes only the item with number = 0 from the collection
permanent.getCounters(game).removeCounter(counterName);
}
countersRemoved += numberOfCountersSelected;
if (!game.isSimulation()) {
game.informPlayers(new StringBuilder(controller.getLogName())

View file

@ -1,9 +1,5 @@
package mage.abilities.decorator;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.Mode;
import mage.abilities.condition.Condition;
@ -11,11 +7,14 @@ import mage.abilities.condition.FixedCondition;
import mage.abilities.condition.LockedInCondition;
import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.constants.DependencyType;
import mage.constants.Duration;
import mage.constants.Layer;
import mage.constants.SubLayer;
import mage.constants.*;
import mage.game.Game;
import org.junit.Assert;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
/**
* Adds condition to {@link ContinuousEffect}. Acts as decorator.
@ -48,6 +47,18 @@ public class ConditionalContinuousEffect extends ContinuousEffectImpl {
this.otherwiseEffect = otherwiseEffect;
this.baseCondition = condition;
this.staticText = text;
// checks for compatibility
EffectType needType = EffectType.CONTINUOUS;
if (effect != null && !effect.getEffectType().equals(needType)) {
Assert.fail("ConditionalContinuousEffect supports only " + needType.toString() + " but found " + effect.getEffectType().toString());
}
if (otherwiseEffect != null && !otherwiseEffect.getEffectType().equals(needType)) {
Assert.fail("ConditionalContinuousEffect supports only " + needType.toString() + " but found " + effect.getEffectType().toString());
}
if (effect != null && otherwiseEffect != null && !effect.getEffectType().equals(otherwiseEffect.getEffectType())) {
Assert.fail("ConditionalContinuousEffect must be same but found " + effect.getEffectType().toString() + " and " + otherwiseEffect.getEffectType().toString());
}
}
public ConditionalContinuousEffect(final ConditionalContinuousEffect effect) {
@ -68,6 +79,7 @@ public class ConditionalContinuousEffect extends ContinuousEffectImpl {
@Override
public void init(Ability source, Game game) {
super.init(source, game);
if (baseCondition instanceof LockedInCondition) {
condition = new FixedCondition(((LockedInCondition) baseCondition).getBaseCondition().apply(game, source));
} else {

View file

@ -0,0 +1,86 @@
package mage.abilities.decorator;
import mage.abilities.Ability;
import mage.abilities.condition.Condition;
import mage.abilities.effects.CostModificationEffect;
import mage.abilities.effects.common.cost.CostModificationEffectImpl;
import mage.constants.Duration;
import mage.game.Game;
/**
* @author JayDi85
*/
public class ConditionalCostModificationEffect extends CostModificationEffectImpl {
protected CostModificationEffect effect;
protected CostModificationEffect otherwiseEffect;
protected Condition condition;
protected boolean conditionState;
public ConditionalCostModificationEffect(CostModificationEffect effect, Condition condition, String text) {
this(effect, condition, null, text);
}
public ConditionalCostModificationEffect(CostModificationEffect effect, Condition condition, CostModificationEffect otherwiseEffect,
String text) {
super(effect.getDuration(), effect.getOutcome(), effect.getModificationType());
this.effect = effect;
this.condition = condition;
this.otherwiseEffect = otherwiseEffect;
if (text != null) {
this.setText(text);
}
}
public ConditionalCostModificationEffect(final ConditionalCostModificationEffect effect) {
super(effect);
this.effect = (CostModificationEffect) effect.effect.copy();
if (effect.otherwiseEffect != null) {
this.otherwiseEffect = (CostModificationEffect) effect.otherwiseEffect.copy();
}
this.condition = effect.condition;
this.conditionState = effect.conditionState;
}
@Override
public boolean isDiscarded() {
return effect.isDiscarded() || (otherwiseEffect != null && otherwiseEffect.isDiscarded());
}
@Override
public boolean apply(Game game, Ability source, Ability abilityToModify) {
conditionState = condition.apply(game, source);
if (conditionState) {
effect.setTargetPointer(this.targetPointer);
return effect.apply(game, source, abilityToModify);
} else if (otherwiseEffect != null) {
otherwiseEffect.setTargetPointer(this.targetPointer);
return otherwiseEffect.apply(game, source, abilityToModify);
}
if (!conditionState && effect.getDuration() == Duration.OneUse) {
used = true;
}
if (!conditionState && effect.getDuration() == Duration.Custom) {
this.discard();
}
return false;
}
@Override
public boolean applies(Ability abilityToModify, Ability source, Game game) {
conditionState = condition.apply(game, source);
if (conditionState) {
effect.setTargetPointer(this.targetPointer);
return effect.applies(abilityToModify, source, game);
} else if (otherwiseEffect != null) {
otherwiseEffect.setTargetPointer(this.targetPointer);
return otherwiseEffect.applies(abilityToModify, source, game);
}
return false;
}
@Override
public ConditionalCostModificationEffect copy() {
return new ConditionalCostModificationEffect(this);
}
}

View file

@ -0,0 +1,140 @@
package mage.abilities.decorator;
import mage.abilities.Ability;
import mage.abilities.Mode;
import mage.abilities.condition.Condition;
import mage.abilities.condition.FixedCondition;
import mage.abilities.condition.LockedInCondition;
import mage.abilities.effects.PreventionEffect;
import mage.abilities.effects.PreventionEffectImpl;
import mage.constants.Duration;
import mage.game.Game;
import mage.game.events.GameEvent;
/**
* @author JayDi85
*/
public class ConditionalPreventionEffect extends PreventionEffectImpl {
protected PreventionEffect effect;
protected PreventionEffect otherwiseEffect;
protected Condition baseCondition;
protected Condition condition;
protected boolean conditionState;
protected boolean initDone = false;
public ConditionalPreventionEffect(PreventionEffect effect, Condition condition, String text) {
this(effect, null, condition, text);
}
/**
* Only use this if both effects have the same layers
*
* @param effect
* @param otherwiseEffect
* @param condition
* @param text
*/
public ConditionalPreventionEffect(PreventionEffect effect, PreventionEffect otherwiseEffect, Condition condition, String text) {
super(effect.getDuration());
this.effect = effect;
this.otherwiseEffect = otherwiseEffect;
this.baseCondition = condition;
this.staticText = text;
}
public ConditionalPreventionEffect(final ConditionalPreventionEffect effect) {
super(effect);
this.effect = (PreventionEffect) effect.effect.copy();
if (effect.otherwiseEffect != null) {
this.otherwiseEffect = (PreventionEffect) effect.otherwiseEffect.copy();
}
this.condition = effect.condition; // TODO: checks conditional copy -- it's can be usefull for memory leaks fix?
this.conditionState = effect.conditionState;
this.baseCondition = effect.baseCondition;
this.initDone = effect.initDone;
}
@Override
public boolean isDiscarded() {
return this.discarded || effect.isDiscarded() || (otherwiseEffect != null && otherwiseEffect.isDiscarded());
}
@Override
public void init(Ability source, Game game) {
super.init(source, game);
if (baseCondition instanceof LockedInCondition) {
condition = new FixedCondition(((LockedInCondition) baseCondition).getBaseCondition().apply(game, source));
} else {
condition = baseCondition;
}
effect.setTargetPointer(this.targetPointer);
effect.init(source, game);
if (otherwiseEffect != null) {
otherwiseEffect.setTargetPointer(this.targetPointer);
otherwiseEffect.init(source, game);
}
initDone = true;
}
@Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
if (conditionState) {
effect.setTargetPointer(this.targetPointer);
return effect.replaceEvent(event, source, game);
} else if (otherwiseEffect != null) {
otherwiseEffect.setTargetPointer(this.targetPointer);
return otherwiseEffect.replaceEvent(event, source, game);
}
if (!conditionState && effect.getDuration() == Duration.OneUse) {
used = true;
}
if (!conditionState && effect.getDuration() == Duration.Custom) {
this.discard();
}
return false;
}
@Override
public boolean apply(Game game, Ability source) {
return false;
}
@Override
public boolean checksEventType(GameEvent event, Game game) {
return effect.checksEventType(event, game)
|| (otherwiseEffect != null && otherwiseEffect.checksEventType(event, game));
}
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
if (!initDone) { // if simpleStaticAbility, init won't be called
init(source, game);
}
conditionState = condition.apply(game, source);
if (conditionState) {
effect.setTargetPointer(this.targetPointer);
return effect.applies(event, source, game);
} else if (otherwiseEffect != null) {
otherwiseEffect.setTargetPointer(this.targetPointer);
return otherwiseEffect.applies(event, source, game);
}
return false;
}
@Override
public String getText(Mode mode) {
if ((staticText == null || staticText.isEmpty()) && this.effect != null) { // usefull for conditional night/day card abilities
return effect.getText(mode);
}
return staticText;
}
@Override
public ConditionalPreventionEffect copy() {
return new ConditionalPreventionEffect(this);
}
}

View file

@ -1,7 +1,5 @@
package mage.abilities.decorator;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.condition.Condition;
import mage.abilities.condition.FixedCondition;
@ -12,12 +10,13 @@ import mage.constants.EffectType;
import mage.game.Game;
import mage.game.permanent.Permanent;
import java.util.UUID;
/**
*
* @author LevelX2
*/
public class ConditionalRequirementEffect extends RequirementEffect {
public class ConditionalRequirementEffect extends RequirementEffect {
protected RequirementEffect effect;
protected RequirementEffect otherwiseEffect;
@ -27,7 +26,14 @@ public class ConditionalRequirementEffect extends RequirementEffect {
protected boolean initDone = false;
public ConditionalRequirementEffect(RequirementEffect effect, Condition condition) {
this(Duration.WhileOnBattlefield, effect, condition, null, false);
this(effect, condition, null);
}
public ConditionalRequirementEffect(RequirementEffect effect, Condition condition, String text) {
this(effect.getDuration(), effect, condition, null, false);
if (text != null) {
setText(text);
}
}
public ConditionalRequirementEffect(Duration duration, RequirementEffect effect, Condition condition, RequirementEffect otherwiseEffect, boolean lockedInCondition) {
@ -75,7 +81,7 @@ public class ConditionalRequirementEffect extends RequirementEffect {
conditionState = condition.apply(game, source);
if (conditionState) {
effect.setTargetPointer(this.targetPointer);
return effect.applies(permanent, source,game);
return effect.applies(permanent, source, game);
} else if (otherwiseEffect != null) {
otherwiseEffect.setTargetPointer(this.targetPointer);
return otherwiseEffect.applies(permanent, source, game);
@ -138,7 +144,7 @@ public class ConditionalRequirementEffect extends RequirementEffect {
}
return null;
}
@Override
public ConditionalRequirementEffect copy() {
return new ConditionalRequirementEffect(this);

View file

@ -22,14 +22,25 @@ public class ConditionalRestrictionEffect extends RestrictionEffect {
protected boolean initDone = false;
public ConditionalRestrictionEffect(RestrictionEffect effect, Condition condition) {
this(Duration.WhileOnBattlefield, effect, condition, null);
this(effect, condition, null);
}
public ConditionalRestrictionEffect(RestrictionEffect effect, Condition condition, String text) {
this(effect.getDuration(), effect, condition, null, text);
}
public ConditionalRestrictionEffect(Duration duration, RestrictionEffect effect, Condition condition, RestrictionEffect otherwiseEffect) {
this(duration, effect, condition, otherwiseEffect, null);
}
public ConditionalRestrictionEffect(Duration duration, RestrictionEffect effect, Condition condition, RestrictionEffect otherwiseEffect, String text) {
super(duration);
this.effect = effect;
this.baseCondition = condition;
this.otherwiseEffect = otherwiseEffect;
if (text != null) {
this.setText(text);
}
}
public ConditionalRestrictionEffect(final ConditionalRestrictionEffect effect) {

View file

@ -4,7 +4,7 @@ package mage.abilities.dynamicvalue.common;
import mage.abilities.Ability;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.effects.Effect;
import mage.filter.common.FilterControlledCreaturePermanent;
import mage.filter.StaticFilters;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
@ -17,17 +17,13 @@ public enum GreatestPowerAmongControlledCreaturesValue implements DynamicValue {
@Override
public int calculate(Game game, Ability sourceAbility, Effect effect) {
Player player = game.getPlayer(sourceAbility.getControllerId());
if (player != null) {
int amount = 0;
for (Permanent p : game.getBattlefield().getActivePermanents(new FilterControlledCreaturePermanent(), sourceAbility.getControllerId(), game)) {
if (p.getPower().getValue() > amount) {
amount = p.getPower().getValue();
}
}
return amount;
int amount = 0;
for (Permanent p : game.getBattlefield().getActivePermanents(
StaticFilters.FILTER_CONTROLLED_CREATURE, sourceAbility.getControllerId(), game
)) {
amount = Math.max(p.getPower().getValue(), amount);
}
return 0;
return amount;
}
@Override

View file

@ -0,0 +1,43 @@
package mage.abilities.dynamicvalue.common;
import mage.abilities.Ability;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.effects.Effect;
import mage.filter.StaticFilters;
import mage.game.Game;
import mage.game.permanent.Permanent;
/**
* @author TheElk801
*/
public enum GreatestToughnessAmongControlledCreaturesValue implements DynamicValue {
instance;
@Override
public int calculate(Game game, Ability sourceAbility, Effect effect) {
int amount = 0;
for (Permanent p : game.getBattlefield().getActivePermanents(
StaticFilters.FILTER_CONTROLLED_CREATURE, sourceAbility.getControllerId(), game
)) {
amount = Math.max(p.getToughness().getValue(), amount);
}
return amount;
}
@Override
public GreatestToughnessAmongControlledCreaturesValue copy() {
return GreatestToughnessAmongControlledCreaturesValue.instance;
}
@Override
public String getMessage() {
return "the greatest toughness among creatures you control";
}
@Override
public String toString() {
return "X";
}
}

View file

@ -0,0 +1,35 @@
package mage.abilities.dynamicvalue.common;
import mage.abilities.Ability;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.effects.Effect;
import mage.filter.StaticFilters;
import mage.game.Game;
/**
* @author JayDi85
*/
public enum PermanentsYouControlCount implements DynamicValue {
instance;
@Override
public int calculate(Game game, Ability sourceAbility, Effect effect) {
return game.getBattlefield().count(StaticFilters.FILTER_CONTROLLED_PERMANENT, sourceAbility.getSourceId(), sourceAbility.getControllerId(), game);
}
@Override
public PermanentsYouControlCount copy() {
return instance;
}
@Override
public String toString() {
return "X";
}
@Override
public String getMessage() {
return "permanents you control";
}
}

View file

@ -1,10 +1,5 @@
package mage.abilities.effects;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.constants.DependencyType;
@ -13,8 +8,12 @@ import mage.constants.Layer;
import mage.constants.SubLayer;
import mage.game.Game;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
/**
*
* @author BetaSteward_at_googlemail.com
*/
public interface ContinuousEffect extends Effect {
@ -41,6 +40,8 @@ public interface ContinuousEffect extends Effect {
void init(Ability source, Game game);
void init(Ability source, Game game, UUID activePlayerId);
Layer getLayer();
SubLayer getSublayer();
@ -59,6 +60,14 @@ public interface ContinuousEffect extends Effect {
void addDependedToType(DependencyType dependencyType);
void setStartingControllerAndTurnNum(Game game, UUID startingController, UUID activePlayerId);
UUID getStartingController();
void incYourTurnNumPlayed();
boolean isYourNextTurn(Game game);
@Override
void newId();

View file

@ -1,12 +1,5 @@
package mage.abilities.effects;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.MageSingleton;
@ -14,18 +7,14 @@ import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.dynamicvalue.common.DomainValue;
import mage.abilities.dynamicvalue.common.SignInversionDynamicValue;
import mage.abilities.dynamicvalue.common.StaticValue;
import mage.constants.AbilityType;
import mage.constants.DependencyType;
import mage.constants.Duration;
import mage.constants.EffectType;
import mage.constants.Layer;
import mage.constants.Outcome;
import mage.constants.SubLayer;
import mage.constants.*;
import mage.game.Game;
import mage.players.Player;
import java.util.*;
/**
* @author BetaSteward_at_googlemail.com
* @author BetaSteward_at_googlemail.com, JayDi85
*/
public abstract class ContinuousEffectImpl extends EffectImpl implements ContinuousEffect {
@ -49,9 +38,10 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu
*/
protected boolean characterDefining = false;
// until your next turn
protected int startingTurn;
protected UUID startingControllerId;
// until your next turn or until end of your next turn
private UUID startingControllerId; // player to checkss turns (can't different with real controller ability)
private boolean startingTurnWasActive;
private int yourTurnNumPlayed = 0; // turnes played after effect was created
public ContinuousEffectImpl(Duration duration, Outcome outcome) {
super(outcome);
@ -79,8 +69,9 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu
this.affectedObjectsSet = effect.affectedObjectsSet;
this.affectedObjectList.addAll(effect.affectedObjectList);
this.temporary = effect.temporary;
this.startingTurn = effect.startingTurn;
this.startingControllerId = effect.startingControllerId;
this.startingTurnWasActive = effect.startingTurnWasActive;
this.yourTurnNumPlayed = effect.yourTurnNumPlayed;
this.dependencyTypes = effect.dependencyTypes;
this.dependendToTypes = effect.dependendToTypes;
this.characterDefining = effect.characterDefining;
@ -148,6 +139,11 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu
@Override
public void init(Ability source, Game game) {
init(source, game, game.getActivePlayerId());
}
@Override
public void init(Ability source, Game game, UUID activePlayerId) {
targetPointer.init(game, source);
//20100716 - 611.2c
if (AbilityType.ACTIVATED == source.getAbilityType()
@ -170,23 +166,75 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu
this.affectedObjectsSet = true;
}
}
startingTurn = game.getTurnNum();
startingControllerId = source.getControllerId();
setStartingControllerAndTurnNum(game, source.getControllerId(), activePlayerId);
}
@Override
public UUID getStartingController() {
return startingControllerId;
}
@Override
public void setStartingControllerAndTurnNum(Game game, UUID startingController, UUID activePlayerId) {
this.startingControllerId = startingController;
this.startingTurnWasActive = activePlayerId != null && activePlayerId.equals(startingController); // you can't use "game" for active player cause it's called from tests/cheat too
this.yourTurnNumPlayed = 0;
}
@Override
public void incYourTurnNumPlayed() {
yourTurnNumPlayed++;
}
@Override
public boolean isYourNextTurn(Game game) {
if (this.startingTurnWasActive) {
return yourTurnNumPlayed == 1 && game.isActivePlayer(startingControllerId);
} else {
return yourTurnNumPlayed == 0 && game.isActivePlayer(startingControllerId);
}
}
@Override
public boolean isInactive(Ability source, Game game) {
if (duration == Duration.UntilYourNextTurn) {
Player player = game.getPlayer(startingControllerId);
if (player != null) {
if (player.isInGame()) {
return game.isActivePlayer(startingControllerId) && game.getTurnNum() != startingTurn;
}
return player.hasReachedNextTurnAfterLeaving();
}
return true;
// YOUR turn checks
// until end of turn - must be checked on cleanup step, see rules 514.2
// other must checked here (active and leave players), see rules 800.4
switch (duration) {
case UntilYourNextTurn:
case UntilEndOfYourNextTurn:
break;
default:
return false;
}
return false;
// cheat engine put cards without play and calls direct applyEffects with clean -- need to ignore it
if (game.getActivePlayerId() == null) {
return false;
}
boolean canDelete = false;
Player player = game.getPlayer(startingControllerId);
// discard on start of turn for leave player
// 800.4i When a player leaves the game, any continuous effects with durations that last until that player's next turn
// or until a specific point in that turn will last until that turn would have begun.
// They neither expire immediately nor last indefinitely.
switch (duration) {
case UntilYourNextTurn:
case UntilEndOfYourNextTurn:
canDelete = player == null || (!player.isInGame() && player.hasReachedNextTurnAfterLeaving());
}
// discard on another conditions (start of your turn)
switch (duration) {
case UntilYourNextTurn:
if (player != null && player.isInGame()) {
canDelete = canDelete || this.isYourNextTurn(game);
}
}
return canDelete;
}
@Override
@ -263,14 +311,6 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu
}
}
return dependentToEffects;
/*
return allEffectsInLayer.stream()
.filter(effect -> effect.getDependencyTypes().contains(dependendToTypes))
.map(Effect::getId)
.collect(Collectors.toSet());
}
return new HashSet<>();*/
}
@Override

View file

@ -1,9 +1,5 @@
package mage.abilities.effects;
import java.io.Serializable;
import java.util.*;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import mage.MageObject;
import mage.MageObjectReference;
import mage.abilities.*;
@ -31,6 +27,11 @@ import mage.players.Player;
import mage.target.common.TargetCardInHand;
import org.apache.log4j.Logger;
import java.io.Serializable;
import java.util.*;
import java.util.Map.Entry;
import java.util.stream.Collectors;
/**
* @author BetaSteward_at_googlemail.com
*/
@ -54,7 +55,7 @@ public class ContinuousEffects implements Serializable {
private final Map<AsThoughEffectType, ContinuousEffectsList<AsThoughEffect>> asThoughEffectsMap = new EnumMap<>(AsThoughEffectType.class);
public final List<ContinuousEffectsList<?>> allEffectsLists = new ArrayList<>();
private final ApplyCountersEffect applyCounters;
// private final PlaneswalkerRedirectionEffect planeswalkerRedirectionEffect;
// private final PlaneswalkerRedirectionEffect planeswalkerRedirectionEffect;
private final AuraReplacementEffect auraReplacementEffect;
private final List<ContinuousEffect> previous = new ArrayList<>();
@ -134,18 +135,18 @@ public class ContinuousEffects implements Serializable {
spliceCardEffects.removeEndOfCombatEffects();
}
public synchronized void removeEndOfTurnEffects() {
layeredEffects.removeEndOfTurnEffects();
continuousRuleModifyingEffects.removeEndOfTurnEffects();
replacementEffects.removeEndOfTurnEffects();
preventionEffects.removeEndOfTurnEffects();
requirementEffects.removeEndOfTurnEffects();
restrictionEffects.removeEndOfTurnEffects();
public synchronized void removeEndOfTurnEffects(Game game) {
layeredEffects.removeEndOfTurnEffects(game);
continuousRuleModifyingEffects.removeEndOfTurnEffects(game);
replacementEffects.removeEndOfTurnEffects(game);
preventionEffects.removeEndOfTurnEffects(game);
requirementEffects.removeEndOfTurnEffects(game);
restrictionEffects.removeEndOfTurnEffects(game);
for (ContinuousEffectsList asThoughtlist : asThoughEffectsMap.values()) {
asThoughtlist.removeEndOfTurnEffects();
asThoughtlist.removeEndOfTurnEffects(game);
}
costModificationEffects.removeEndOfTurnEffects();
spliceCardEffects.removeEndOfTurnEffects();
costModificationEffects.removeEndOfTurnEffects(game);
spliceCardEffects.removeEndOfTurnEffects(game);
}
public synchronized void removeInactiveEffects(Game game) {
@ -163,6 +164,20 @@ public class ContinuousEffects implements Serializable {
spliceCardEffects.removeInactiveEffects(game);
}
public synchronized void incYourTurnNumPlayed(Game game) {
layeredEffects.incYourTurnNumPlayed(game);
continuousRuleModifyingEffects.incYourTurnNumPlayed(game);
replacementEffects.incYourTurnNumPlayed(game);
preventionEffects.incYourTurnNumPlayed(game);
requirementEffects.incYourTurnNumPlayed(game);
restrictionEffects.incYourTurnNumPlayed(game);
for (ContinuousEffectsList asThoughtlist : asThoughEffectsMap.values()) {
asThoughtlist.incYourTurnNumPlayed(game);
}
costModificationEffects.incYourTurnNumPlayed(game);
spliceCardEffects.incYourTurnNumPlayed(game);
}
public synchronized List<ContinuousEffect> getLayeredEffects(Game game) {
List<ContinuousEffect> layerEffects = new ArrayList<>();
for (ContinuousEffect effect : layeredEffects) {
@ -322,7 +337,7 @@ public class ContinuousEffects implements Serializable {
}
// boolean checkLKI = event.getType().equals(EventType.ZONE_CHANGE) || event.getType().equals(EventType.DESTROYED_PERMANENT);
//get all applicable transient Replacement effects
for (Iterator<ReplacementEffect> iterator = replacementEffects.iterator(); iterator.hasNext();) {
for (Iterator<ReplacementEffect> iterator = replacementEffects.iterator(); iterator.hasNext(); ) {
ReplacementEffect effect = iterator.next();
if (!effect.checksEventType(event, game)) {
continue;
@ -354,7 +369,8 @@ public class ContinuousEffects implements Serializable {
replaceEffects.put(effect, applicableAbilities);
}
}
for (Iterator<PreventionEffect> iterator = preventionEffects.iterator(); iterator.hasNext();) {
for (Iterator<PreventionEffect> iterator = preventionEffects.iterator(); iterator.hasNext(); ) {
PreventionEffect effect = iterator.next();
if (!effect.checksEventType(event, game)) {
continue;
@ -376,9 +392,10 @@ public class ContinuousEffects implements Serializable {
}
}
if (!applicableAbilities.isEmpty()) {
replaceEffects.put((ReplacementEffect) effect, applicableAbilities);
replaceEffects.put(effect, applicableAbilities);
}
}
return replaceEffects;
}
@ -478,7 +495,6 @@ public class ContinuousEffects implements Serializable {
}
/**
*
* @param objectId
* @param type
* @param affectedAbility
@ -697,10 +713,10 @@ public class ContinuousEffects implements Serializable {
* Checks if an event won't happen because of an rule modifying effect
*
* @param event
* @param targetAbility ability the event is attached to. can be null.
* @param targetAbility ability the event is attached to. can be null.
* @param game
* @param checkPlayableMode true if the event does not really happen but
* it's checked if the event would be replaced
* it's checked if the event would be replaced
* @return
*/
public boolean preventedByRuleModification(GameEvent event, Ability targetAbility, Game game, boolean checkPlayableMode) {
@ -747,7 +763,7 @@ public class ContinuousEffects implements Serializable {
do {
Map<ReplacementEffect, Set<Ability>> rEffects = getApplicableReplacementEffects(event, game);
// Remove all consumed effects (ability dependant)
for (Iterator<ReplacementEffect> it1 = rEffects.keySet().iterator(); it1.hasNext();) {
for (Iterator<ReplacementEffect> it1 = rEffects.keySet().iterator(); it1.hasNext(); ) {
ReplacementEffect entry = it1.next();
if (consumed.containsKey(entry.getId()) /*&& !(entry instanceof CommanderReplacementEffect) */) { // 903.9.
Set<UUID> consumedAbilitiesIds = consumed.get(entry.getId());
@ -938,7 +954,7 @@ public class ContinuousEffects implements Serializable {
if (!waitingEffects.isEmpty()) {
// check if waiting effects can be applied now
for (Iterator<Map.Entry<ContinuousEffect, Set<UUID>>> iterator = waitingEffects.entrySet().iterator(); iterator.hasNext();) {
for (Iterator<Map.Entry<ContinuousEffect, Set<UUID>>> iterator = waitingEffects.entrySet().iterator(); iterator.hasNext(); ) {
Map.Entry<ContinuousEffect, Set<UUID>> entry = iterator.next();
if (appliedEffects.containsAll(entry.getValue())) { // all dependent to effects are applied now so apply the effect itself
appliedAbilities = appliedEffectAbilities.get(entry.getKey());
@ -1059,9 +1075,7 @@ public class ContinuousEffects implements Serializable {
final Card card = game.getPermanentOrLKIBattlefield(ability.getSourceId());
if (!(effect instanceof BecomesFaceDownCreatureEffect)) {
if (card != null) {
if (!card.getAbilities(game).contains(ability)) {
return false;
}
return card.getAbilities(game).contains(ability);
}
}
return true;

View file

@ -1,13 +1,17 @@
package mage.abilities.effects;
import java.util.*;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.MageSingleton;
import mage.cards.Card;
import mage.constants.Duration;
import mage.constants.Zone;
import mage.game.Game;
import mage.players.Player;
import org.apache.log4j.Logger;
import java.util.*;
/**
* @param <T>
* @author BetaSteward_at_googlemail.com
@ -40,10 +44,21 @@ public class ContinuousEffectsList<T extends ContinuousEffect> extends ArrayList
return new ContinuousEffectsList<>(this);
}
public void removeEndOfTurnEffects() {
for (Iterator<T> i = this.iterator(); i.hasNext();) {
public void removeEndOfTurnEffects(Game game) {
// calls every turn on cleanup step (only end of turn duration)
// rules 514.2
for (Iterator<T> i = this.iterator(); i.hasNext(); ) {
T entry = i.next();
if (entry.getDuration() == Duration.EndOfTurn) {
boolean canRemove = false;
switch (entry.getDuration()) {
case EndOfTurn:
canRemove = true;
break;
case UntilEndOfYourNextTurn:
canRemove = entry.isYourNextTurn(game);
break;
}
if (canRemove) {
i.remove();
effectAbilityMap.remove(entry.getId());
}
@ -52,7 +67,7 @@ public class ContinuousEffectsList<T extends ContinuousEffect> extends ArrayList
public void removeEndOfCombatEffects() {
for (Iterator<T> i = this.iterator(); i.hasNext();) {
for (Iterator<T> i = this.iterator(); i.hasNext(); ) {
T entry = i.next();
if (entry.getDuration() == Duration.EndOfCombat) {
i.remove();
@ -62,7 +77,7 @@ public class ContinuousEffectsList<T extends ContinuousEffect> extends ArrayList
}
public void removeInactiveEffects(Game game) {
for (Iterator<T> i = this.iterator(); i.hasNext();) {
for (Iterator<T> i = this.iterator(); i.hasNext(); ) {
T entry = i.next();
if (isInactive(entry, game)) {
i.remove();
@ -71,7 +86,32 @@ public class ContinuousEffectsList<T extends ContinuousEffect> extends ArrayList
}
}
public void incYourTurnNumPlayed(Game game) {
for (Iterator<T> i = this.iterator(); i.hasNext(); ) {
T entry = i.next();
if (game.isActivePlayer(entry.getStartingController())) {
entry.incYourTurnNumPlayed();
}
}
}
private boolean isInactive(T effect, Game game) {
// ends all inactive effects -- calls on player leave or apply new effect
if (game.getState().isGameOver()) {
// no need to remove effects after end -- users and tests must see last game state
return false;
}
/*
800.4a When a player leaves the game, all objects (see rule 109) owned by that player leave the game and any effects
which give that player control of any objects or players end. Then, if that player controlled any objects on the stack
not represented by cards, those objects cease to exist. Then, if there are any objects still controlled by that player,
those objects are exiled. This is not a state-based action. It happens as soon as the player leaves the game.
If the player who left the game had priority at the time he or she left, priority passes to the next player in turn
order whos still in the game.
*/
// objects removes doing in player.leave() call... effects removes is here
Set<Ability> set = effectAbilityMap.get(effect.getId());
if (set == null) {
logger.debug("No abilities for effect found: " + effect.toString());
@ -87,29 +127,62 @@ public class ContinuousEffectsList<T extends ContinuousEffect> extends ArrayList
} else if (effect.isDiscarded()) {
it.remove();
} else {
// 800.4k When a player leaves the game, any continuous effects with durations that last until that
// players next turn or until a specific point in that turn will last until that turn would have begun.
// They neither expire immediately nor last indefinitely.
MageObject object = game.getObject(ability.getSourceId());
boolean isObjectInGame = ability.getSourceId() == null || object != null; // Commander effects have no sourceId
boolean isOwnerLeaveGame = false;
if (object instanceof Card) {
Player owner = game.getPlayer(((Card) object).getOwnerId());
isOwnerLeaveGame = !owner.isInGame();
}
switch (effect.getDuration()) {
//
case WhileOnBattlefield:
case WhileInGraveyard:
case WhileOnStack:
if (ability.getSourceId() != null && game.getObject(ability.getSourceId()) == null) { // Commander effects have no sourceId
it.remove(); // if the related source object does no longer exist in game - the effect has to be removed
case EndOfStep:
case EndOfCombat:
case EndOfGame:
// if the related source object does no longer exist in game - the effect has to be removed
if (isOwnerLeaveGame || !isObjectInGame) {
it.remove();
}
break;
case OneUse:
if (effect.isUsed()) {
if (isOwnerLeaveGame || effect.isUsed()) {
it.remove();
}
break;
case Custom:
// custom effects must process it's own inactive method (override), but can'be missied by devs
if (isOwnerLeaveGame || effect.isInactive(ability, game)) {
it.remove();
}
break;
case EndOfTurn:
// end of turn discards on cleanup steps
// 514.2
break;
case UntilYourNextTurn:
case UntilEndOfYourNextTurn:
// until your turn effects continue until real turn reached, their used it's own inactive method
// 514.2 Second, the following actions happen simultaneously: all damage marked on permanents
// (including phased-out permanents) is removed and all "until end of turn" and "this turn" effects end.
// This turn-based action doesnt use the stack.
if (effect.isInactive(ability, game)) {
it.remove();
}
break;
case UntilSourceLeavesBattlefield:
if (Zone.BATTLEFIELD != game.getState().getZone(ability.getSourceId())) {
if (isOwnerLeaveGame || Zone.BATTLEFIELD != game.getState().getZone(ability.getSourceId())) {
it.remove();
}
break;
default:
throw new IllegalStateException("Effects gets unknown duration " + effect.getDuration() + ", effect: " + effect.toString());
}
}
}
@ -151,7 +224,7 @@ public class ContinuousEffectsList<T extends ContinuousEffect> extends ArrayList
abilities.removeAll(abilitiesToRemove);
}
if (abilities == null || abilities.isEmpty()) {
for (Iterator<T> iterator = this.iterator(); iterator.hasNext();) {
for (Iterator<T> iterator = this.iterator(); iterator.hasNext(); ) {
ContinuousEffect effect = iterator.next();
if (effect.getId().equals(effectIdToRemove)) {
iterator.remove();

View file

@ -0,0 +1,60 @@
/*
* 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;
import mage.abilities.Ability;
import mage.constants.Duration;
import mage.counters.CounterType;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
/**
*
* @author antoni-g
*/
public class PreventDamageAndRemoveCountersEffect extends PreventionEffectImpl {
public PreventDamageAndRemoveCountersEffect() {
super(Duration.WhileOnBattlefield, Integer.MAX_VALUE, false, false);
staticText = "If damage would be dealt to {this}, prevent that damage and remove that many +1/+1 counters from it";
}
public PreventDamageAndRemoveCountersEffect(final PreventDamageAndRemoveCountersEffect effect) {
super(effect);
}
@Override
public PreventDamageAndRemoveCountersEffect copy() {
return new PreventDamageAndRemoveCountersEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
return true;
}
@Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
int damage = event.getAmount();
preventDamageAction(event, source, game);
Permanent permanent = game.getPermanent(source.getSourceId());
if (permanent != null) {
permanent.removeCounters(CounterType.P1P1.createInstance(damage), game); //MTG ruling (this) loses counters even if the damage isn't prevented
}
return false;
}
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
if (super.applies(event, source, game)) {
if (event.getTargetId().equals(source.getSourceId())) {
return true;
}
}
return false;
}
}

View file

@ -13,7 +13,6 @@ import mage.players.Player;
import mage.util.CardUtil;
/**
*
* @author LevelX2
*/
public class ChooseACardNameEffect extends OneShotEffect {
@ -92,11 +91,11 @@ public class ChooseACardNameEffect extends OneShotEffect {
if (controller.choose(Outcome.Detriment, cardChoice, game)) {
String cardName = cardChoice.getChoice();
if (!game.isSimulation()) {
game.informPlayers(sourceObject.getLogName() + ", named card: [" + cardName + ']');
game.informPlayers(sourceObject.getLogName() + ", chosen name: [" + cardName + ']');
}
game.getState().setValue(source.getSourceId().toString() + INFO_KEY, cardName);
if (sourceObject instanceof Permanent) {
((Permanent) sourceObject).addInfo(INFO_KEY, CardUtil.addToolTipMarkTags("Named card: " + cardName), game);
((Permanent) sourceObject).addInfo(INFO_KEY, CardUtil.addToolTipMarkTags("Chosen name: " + cardName), game);
}
return true;
}

View file

@ -122,8 +122,12 @@ public class CopyEffect extends ContinuousEffectImpl {
permanent.addAbility(ability, getSourceId(), game, false); // no new Id so consumed replacement effects are known while new continuousEffects.apply happen.
}
}
permanent.getPower().setValue(copyFromObject.getPower().getValue());
permanent.getToughness().setValue(copyFromObject.getToughness().getValue());
// Primal Clay example:
// If a creature thats already on the battlefield becomes a copy of this creature, it copies the power, toughness,
// and abilities that were chosen for this creature as it entered the battlefield. (2018-03-16)
permanent.getPower().setValue(copyFromObject.getPower().getBaseValueModified());
permanent.getToughness().setValue(copyFromObject.getToughness().getBaseValueModified());
if (copyFromObject instanceof Permanent) {
Permanent targetPermanent = (Permanent) copyFromObject;
permanent.setTransformed(targetPermanent.isTransformed());

View file

@ -1,4 +1,3 @@
package mage.abilities.effects.common;
import mage.abilities.Ability;
@ -70,8 +69,8 @@ public class PreventDamageToAttachedEffect extends PreventionEffectImpl {
}
sb.append("damage to ");
sb.append(attachmentType.verb());
sb.append("creature, prevent ").append(amountToPrevent);;
sb.append("of that damage");
sb.append(" creature, prevent ").append(amountToPrevent);;
sb.append(" of that damage");
}
return sb.toString();
}

View file

@ -41,7 +41,7 @@ public class RecruiterEffect extends OneShotEffect {
if (controller != null) {
TargetCardInLibrary targetCards = new TargetCardInLibrary(0, Integer.MAX_VALUE, filter);
Cards cards = new CardsImpl();
if (controller.searchLibrary(targetCards, game)) {
if (controller.searchLibrary(targetCards, source, game)) {
cards.addAll(targetCards.getTargets());
}
controller.revealCards(staticText, cards, game);

View file

@ -44,7 +44,7 @@ public class ReplaceOpponentCardsInHandWithSelectedEffect extends OneShotEffect
TargetCardInLibrary target = new TargetCardInLibrary(searchLibraryForNum, searchLibraryForNum, new FilterCard());
controller.searchLibrary(target, game, targetOpponent.getId());
controller.searchLibrary(target, source, game, targetOpponent.getId());
for (UUID cardId : target.getTargets()) {
Card targetCard = game.getCard(cardId);

View file

@ -1,9 +1,10 @@
package mage.abilities.effects.common;
import mage.abilities.Ability;
import mage.abilities.Mode;
import mage.abilities.effects.OneShotEffect;
import mage.cards.Card;
import mage.cards.Cards;
import mage.cards.CardsImpl;
import mage.constants.Outcome;
import mage.constants.Zone;
@ -34,8 +35,15 @@ public class ReturnFromGraveyardToHandTargetEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
if (controller != null) {
return controller.moveCards(new CardsImpl(getTargetPointer().getTargets(game, source)), Zone.HAND, source, game);
if (controller == null) {
return false;
}
Cards cardsInGraveyard = new CardsImpl(getTargetPointer().getTargets(game, source));
for (Card card : cardsInGraveyard.getCards(game)) {
if (card != null
&& game.getState().getZone(card.getId()) == Zone.GRAVEYARD) {
controller.moveCards(card, Zone.HAND, source, game); //verify the target card is still in the graveyard
}
}
return false;
}

View file

@ -33,7 +33,7 @@ public class CantAttackYouAllEffect extends RestrictionEffect {
this.alsoPlaneswalker = alsoPlaneswalker;
staticText = filterAttacker.getMessage() + " can't attack you"
+ (alsoPlaneswalker ? " or a planeswalker you control" : "")
+ (duration == Duration.UntilYourNextTurn ? " until your next turn" : "");
+ (duration == Duration.UntilYourNextTurn || duration == Duration.UntilEndOfYourNextTurn ? " " + duration.toString() : "");
}
CantAttackYouAllEffect(final CantAttackYouAllEffect effect) {

View file

@ -1,19 +1,13 @@
package mage.abilities.effects.common.continuous;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.Mode;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.Layer;
import mage.constants.Outcome;
import mage.constants.SubLayer;
import mage.constants.*;
import mage.filter.FilterPermanent;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.game.permanent.token.TokenImpl;
import mage.game.permanent.token.Token;
import java.util.HashSet;
@ -21,7 +15,6 @@ import java.util.Set;
/**
* @author LevelX2
*
*/
public class BecomesCreatureAllEffect extends ContinuousEffectImpl {
@ -29,13 +22,19 @@ public class BecomesCreatureAllEffect extends ContinuousEffectImpl {
protected String theyAreStillType;
private final FilterPermanent filter;
private boolean loseColor = true;
protected boolean loseName = false;
public BecomesCreatureAllEffect(Token token, String theyAreStillType, FilterPermanent filter, Duration duration, boolean loseColor) {
this(token, theyAreStillType, filter, duration, loseColor, false);
}
public BecomesCreatureAllEffect(Token token, String theyAreStillType, FilterPermanent filter, Duration duration, boolean loseColor, boolean loseName) {
super(duration, Outcome.BecomeCreature);
this.token = token;
this.theyAreStillType = theyAreStillType;
this.filter = filter;
this.loseColor = loseColor;
this.loseName = loseName;
}
public BecomesCreatureAllEffect(final BecomesCreatureAllEffect effect) {
@ -44,6 +43,7 @@ public class BecomesCreatureAllEffect extends ContinuousEffectImpl {
this.theyAreStillType = effect.theyAreStillType;
this.filter = effect.filter.copy();
this.loseColor = effect.loseColor;
this.loseName = effect.loseName;
}
@Override
@ -65,30 +65,47 @@ public class BecomesCreatureAllEffect extends ContinuousEffectImpl {
public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) {
Set<Permanent> affectedPermanents = new HashSet<>();
if (this.affectedObjectsSet) {
for(MageObjectReference ref : affectedObjectList) {
for (MageObjectReference ref : affectedObjectList) {
affectedPermanents.add(ref.getPermanent(game));
}
} else {
affectedPermanents = new HashSet<>(game.getBattlefield().getActivePermanents(filter, source.getControllerId(), source.getSourceId(), game));
}
for(Permanent permanent : affectedPermanents) {
for (Permanent permanent : affectedPermanents) {
if (permanent != null) {
switch (layer) {
case TextChangingEffects_3:
if (sublayer == SubLayer.NA) {
if (loseName) {
permanent.setName(token.getName());
}
}
break;
case TypeChangingEffects_4:
if (sublayer == SubLayer.NA) {
if (!token.getCardType().isEmpty()) {
for (CardType t : token.getCardType()) {
if (!permanent.getCardType().contains(t)) {
permanent.addCardType(t);
if (theyAreStillType != null) {
permanent.getSubtype(game).retainAll(SubType.getLandTypes());
permanent.getSubtype(game).addAll(token.getSubtype(game));
} else {
for (SubType t : token.getSubtype(game)) {
if (!permanent.hasSubtype(t, game)) {
permanent.getSubtype(game).add(t);
}
}
}
if (theyAreStillType == null) {
permanent.getSubtype(game).clear();
for (SuperType t : token.getSuperType()) {
if (!permanent.getSuperType().contains(t)) {
permanent.addSuperType(t);
}
}
if (!token.getSubtype(game).isEmpty()) {
permanent.getSubtype(game).addAll(token.getSubtype(game));
for (CardType t : token.getCardType()) {
if (!permanent.getCardType().contains(t)) {
permanent.addCardType(t);
}
}
}
break;
@ -141,7 +158,11 @@ public class BecomesCreatureAllEffect extends ContinuousEffectImpl {
@Override
public boolean hasLayer(Layer layer) {
return layer == Layer.PTChangingEffects_7 || layer == Layer.AbilityAddingRemovingEffects_6 || layer == Layer.ColorChangingEffects_5 || layer == Layer.TypeChangingEffects_4;
return layer == Layer.PTChangingEffects_7
|| layer == Layer.AbilityAddingRemovingEffects_6
|| layer == Layer.ColorChangingEffects_5
|| layer == Layer.TypeChangingEffects_4
|| layer == Layer.TextChangingEffects_3;
}
@Override

View file

@ -20,25 +20,34 @@ public class BecomesCreatureTargetEffect extends ContinuousEffectImpl {
protected Token token;
protected boolean loseAllAbilities;
protected boolean addStillALandText;
protected boolean loseName;
public BecomesCreatureTargetEffect(Token token, boolean loseAllAbilities, boolean stillALand, Duration duration) {
this(token, loseAllAbilities, stillALand, duration, false);
}
/**
* @param token
* @param loseAllAbilities loses all subtypes and colors
* @param stillALand add rule text, "it's still a land"
* @param loseName permanent lose name and get's it from token
* @param duration
*/
public BecomesCreatureTargetEffect(Token token, boolean loseAllAbilities, boolean stillALand, Duration duration) {
public BecomesCreatureTargetEffect(Token token, boolean loseAllAbilities, boolean stillALand, Duration duration, boolean loseName) {
super(duration, Outcome.BecomeCreature);
this.token = token;
this.loseAllAbilities = loseAllAbilities;
this.addStillALandText = stillALand;
this.loseName = loseName;
}
public BecomesCreatureTargetEffect(final BecomesCreatureTargetEffect effect) {
super(effect);
token = effect.token.copy();
this.token = effect.token.copy();
this.loseAllAbilities = effect.loseAllAbilities;
this.addStillALandText = effect.addStillALandText;
this.loseName = effect.loseName;
}
@Override
@ -53,30 +62,41 @@ public class BecomesCreatureTargetEffect extends ContinuousEffectImpl {
Permanent permanent = game.getPermanent(permanentId);
if (permanent != null) {
switch (layer) {
case TextChangingEffects_3:
if (sublayer == SubLayer.NA) {
if (loseName) {
permanent.setName(token.getName());
}
}
break;
case TypeChangingEffects_4:
if (sublayer == SubLayer.NA) {
if (loseAllAbilities) {
permanent.getSubtype(game).retainAll(SubType.getLandTypes());
permanent.getSubtype(game).addAll(token.getSubtype(game));
} else {
if (!token.getSubtype(game).isEmpty()) {
for (SubType subtype : token.getSubtype(game)) {
if (!permanent.hasSubtype(subtype, game)) {
permanent.getSubtype(game).add(subtype);
}
for (SubType t : token.getSubtype(game)) {
if (!permanent.hasSubtype(t, game)) {
permanent.getSubtype(game).add(t);
}
}
}
if (!token.getCardType().isEmpty()) {
for (CardType t : token.getCardType()) {
if (!permanent.getCardType().contains(t)) {
permanent.addCardType(t);
}
for (SuperType t : token.getSuperType()) {
if (!permanent.getSuperType().contains(t)) {
permanent.addSuperType(t);
}
}
for (CardType t : token.getCardType()) {
if (!permanent.getCardType().contains(t)) {
permanent.addCardType(t);
}
}
}
break;
case ColorChangingEffects_5:
if (sublayer == SubLayer.NA) {
if (loseAllAbilities) {
@ -91,6 +111,7 @@ public class BecomesCreatureTargetEffect extends ContinuousEffectImpl {
}
}
break;
case AbilityAddingRemovingEffects_6:
if (loseAllAbilities) {
permanent.removeAllAbilities(source.getSourceId(), game);
@ -125,7 +146,11 @@ public class BecomesCreatureTargetEffect extends ContinuousEffectImpl {
@Override
public boolean hasLayer(Layer layer) {
return layer == Layer.PTChangingEffects_7 || layer == Layer.AbilityAddingRemovingEffects_6 || layer == Layer.ColorChangingEffects_5 || layer == Layer.TypeChangingEffects_4;
return layer == Layer.PTChangingEffects_7
|| layer == Layer.AbilityAddingRemovingEffects_6
|| layer == Layer.ColorChangingEffects_5
|| layer == Layer.TypeChangingEffects_4
|| layer == Layer.TextChangingEffects_3;
}
@Override

View file

@ -0,0 +1,50 @@
package mage.abilities.effects.common.continuous;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.cards.Card;
import mage.constants.Duration;
import mage.constants.Layer;
import mage.constants.Outcome;
import mage.constants.SubLayer;
import mage.game.Game;
import mage.players.Player;
/**
* @author TheElk801
*/
public class LookAtTopCardOfLibraryAnyTimeEffect extends ContinuousEffectImpl {
public LookAtTopCardOfLibraryAnyTimeEffect() {
super(Duration.WhileOnBattlefield, Layer.PlayerEffects, SubLayer.NA, Outcome.Benefit);
staticText = "You may look at the top card of your library any time.";
}
private LookAtTopCardOfLibraryAnyTimeEffect(final LookAtTopCardOfLibraryAnyTimeEffect effect) {
super(effect);
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
if (controller == null) {
return true;
}
Card topCard = controller.getLibrary().getFromTop(game);
if (topCard == null) {
return true;
}
MageObject obj = source.getSourceObject(game);
if (obj == null) {
return true;
}
controller.lookAtCards("Top card of " + obj.getIdName() + " controller's library", topCard, game);
return true;
}
@Override
public LookAtTopCardOfLibraryAnyTimeEffect copy() {
return new LookAtTopCardOfLibraryAnyTimeEffect(this);
}
}

View file

@ -33,13 +33,13 @@ public class UntapAllDuringEachOtherPlayersUntapStepEffect extends ContinuousEff
@Override
public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) {
Boolean applied = (Boolean) game.getState().getValue(source.getSourceId() + "applied");
if (applied == null) {
applied = Boolean.FALSE;
}
if (!applied && layer == Layer.RulesEffects) {
if (!source.isControlledBy(game.getActivePlayerId()) && game.getStep().getType() == PhaseStep.UNTAP) {
game.getState().setValue(source.getSourceId() + "applied", true);
if (layer == Layer.RulesEffects && game.getStep().getType() == PhaseStep.UNTAP && !source.isControlledBy(game.getActivePlayerId())) {
Integer appliedTurn = (Integer) game.getState().getValue(source.getSourceId() + "appliedTurn");
if (appliedTurn == null) {
appliedTurn = 0;
}
if (appliedTurn < game.getTurnNum()) {
game.getState().setValue(source.getSourceId() + "appliedTurn", game.getTurnNum());
for (Permanent permanent : game.getBattlefield().getAllActivePermanents(filter, source.getControllerId(), game)) {
boolean untap = true;
for (RestrictionEffect effect : game.getContinuousEffects().getApplicableRestrictionEffects(permanent, game).keySet()) {
@ -50,10 +50,6 @@ public class UntapAllDuringEachOtherPlayersUntapStepEffect extends ContinuousEff
}
}
}
} else if (applied && layer == Layer.RulesEffects) {
if (game.getStep().getType() == PhaseStep.END_TURN) {
game.getState().setValue(source.getSourceId() + "applied", false);
}
}
return true;
}

View file

@ -1,4 +1,3 @@
package mage.abilities.effects.common.cost;
import mage.abilities.Ability;
@ -13,14 +12,17 @@ import mage.game.Game;
import mage.util.CardUtil;
/**
*
* @author LevelX2
*/
public class SpellCostReductionSourceEffect extends CostModificationEffectImpl {
private final int amount;
private ManaCosts<ManaCost> manaCostsToReduce = null;
private final Condition condition;
private Condition condition;
public SpellCostReductionSourceEffect(ManaCosts<ManaCost> manaCostsToReduce) {
this(manaCostsToReduce, null);
}
public SpellCostReductionSourceEffect(ManaCosts<ManaCost> manaCostsToReduce, Condition condition) {
super(Duration.WhileOnBattlefield, Outcome.Benefit, CostModificationType.REDUCE_COST);
@ -33,19 +35,28 @@ public class SpellCostReductionSourceEffect extends CostModificationEffectImpl {
for (String manaSymbol : manaCostsToReduce.getSymbols()) {
sb.append(manaSymbol);
}
sb.append(" less to if ").append(this.condition.toString());
sb.append(" less");
if (this.condition != null) {
sb.append(" to if ").append(this.condition.toString());
}
this.staticText = sb.toString();
}
public SpellCostReductionSourceEffect(int amount) {
this(amount, null);
}
public SpellCostReductionSourceEffect(int amount, Condition condition) {
super(Duration.WhileOnBattlefield, Outcome.Benefit, CostModificationType.REDUCE_COST);
this.amount = amount;
this.condition = condition;
StringBuilder sb = new StringBuilder();
sb.append("{this} costs {")
.append(amount).append("} less to cast ")
.append((this.condition.toString().startsWith("if ") ? "" : "if "))
.append(this.condition.toString());
sb.append("{this} costs {").append(amount).append("} less to cast");
if (this.condition != null) {
sb.append(" ").append(this.condition.toString().startsWith("if ") ? "" : "if ");
sb.append(this.condition.toString());
}
this.staticText = sb.toString();
}
@ -69,7 +80,7 @@ public class SpellCostReductionSourceEffect extends CostModificationEffectImpl {
@Override
public boolean applies(Ability abilityToModify, Ability source, Game game) {
if (abilityToModify.getSourceId().equals(source.getSourceId()) && (abilityToModify instanceof SpellAbility)) {
return condition.apply(game, source);
return condition == null || condition.apply(game, source);
}
return false;
}

View file

@ -1,23 +1,20 @@
package mage.abilities.effects.common.counter;
import java.io.Serializable;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect;
import mage.choices.Choice;
import mage.choices.ChoiceImpl;
import mage.constants.Outcome;
import mage.counters.Counter;
import mage.filter.common.FilterPermanentOrPlayerWithCounter;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.Target;
import mage.target.common.TargetPermanentOrPlayerWithCounter;
import mage.target.common.TargetPermanentOrPlayer;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* @author nantuko
@ -25,8 +22,19 @@ import mage.target.common.TargetPermanentOrPlayerWithCounter;
public class ProliferateEffect extends OneShotEffect {
public ProliferateEffect() {
this("", true);
}
public ProliferateEffect(boolean showAbilityHint) {
this("", showAbilityHint);
}
public ProliferateEffect(String afterText, boolean showAbilityHint) {
super(Outcome.Benefit);
staticText = "proliferate. <i>(You choose any number of permanents and/or players with counters on them, then give each another counter of each kind already there.)</i>";
staticText = "proliferate" + afterText;
if (showAbilityHint) {
staticText += ". <i>(You choose any number of permanents and/or players with counters on them, then give each another counter of each kind already there.)</i>";
}
}
public ProliferateEffect(ProliferateEffect effect) {
@ -36,10 +44,11 @@ public class ProliferateEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
Counter newCounter = null;
if (controller == null) {
return false;
}
Target target = new TargetPermanentOrPlayerWithCounter(0, Integer.MAX_VALUE, true);
Target target = new TargetPermanentOrPlayer(0, Integer.MAX_VALUE, new FilterPermanentOrPlayerWithCounter(), true);
Map<String, Serializable> options = new HashMap<>();
options.put("UI.right.btn.text", "Done");
controller.choose(Outcome.Benefit, target, source.getSourceId(), game, options);
@ -49,18 +58,30 @@ public class ProliferateEffect extends OneShotEffect {
if (permanent != null) {
if (!permanent.getCounters(game).isEmpty()) {
for (Counter counter : permanent.getCounters(game).values()) {
Counter newCounter = new Counter(counter.getName());
newCounter = new Counter(counter.getName());
permanent.addCounters(newCounter, source, game);
}
if (newCounter != null) {
game.informPlayers(permanent.getName()
+ " had 1 "
+ newCounter.getName()
+ " counter added to it.");
}
}
} else {
Player player = game.getPlayer(chosen);
if (player != null) {
if (!player.getCounters().isEmpty()) {
for (Counter counter : player.getCounters().values()) {
Counter newCounter = new Counter(counter.getName());
newCounter = new Counter(counter.getName());
player.addCounters(newCounter, game);
}
if (newCounter != null) {
game.informPlayers(player.getName()
+ " had 1 "
+ newCounter.getName()
+ " counter added to them.");
}
}
}
}

View file

@ -106,7 +106,7 @@ public class DiscardCardYouChooseTargetEffect extends OneShotEffect {
Cards revealedCards = new CardsImpl();
numberToReveal = Math.min(player.getHand().size(), numberToReveal);
if (player.getHand().size() > numberToReveal) {
TargetCardInHand chosenCards = new TargetCardInHand(numberToReveal, numberToReveal, new FilterCard("card in " + player.getLogName() + "'s hand"));
TargetCardInHand chosenCards = new TargetCardInHand(numberToReveal, numberToReveal, new FilterCard("card in " + player.getName() + "'s hand"));
chosenCards.setNotTarget(true);
if (chosenCards.canChoose(player.getId(), game) && player.chooseTarget(Outcome.Discard, player.getHand(), chosenCards, source, game)) {
if (!chosenCards.getTargets().isEmpty()) {

View file

@ -59,7 +59,7 @@ public class SearchLibraryGraveyardPutInHandEffect extends OneShotEffect {
if (forceToSearchBoth || controller.chooseUse(outcome, "Search your library for a card named " + filter.getMessage() + '?', source, game)) {
TargetCardInLibrary target = new TargetCardInLibrary(0, 1, filter);
target.clearChosen();
if (controller.searchLibrary(target, game)) {
if (controller.searchLibrary(target, source, game)) {
if (!target.getTargets().isEmpty()) {
cardFound = game.getCard(target.getFirstTarget());
}

View file

@ -0,0 +1,91 @@
package mage.abilities.effects.common.search;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect;
import mage.cards.Card;
import mage.cards.CardsImpl;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.filter.FilterCard;
import mage.game.Game;
import mage.players.Player;
import mage.target.TargetCard;
import mage.target.common.TargetCardInLibrary;
/**
*
* @author Styxo
*/
public class SearchLibraryGraveyardPutOntoBattlefieldEffect extends OneShotEffect {
private FilterCard filter;
private boolean forceToSearchBoth;
public SearchLibraryGraveyardPutOntoBattlefieldEffect(FilterCard filter) {
this(filter, false);
}
public SearchLibraryGraveyardPutOntoBattlefieldEffect(FilterCard filter, boolean forceToSearchBoth) {
this(filter, forceToSearchBoth, false);
}
public SearchLibraryGraveyardPutOntoBattlefieldEffect(FilterCard filter, boolean forceToSearchBoth, boolean youMay) {
super(Outcome.Benefit);
this.filter = filter;
this.forceToSearchBoth = forceToSearchBoth;
staticText = (youMay ? "You may" : "") + " search your library and" + (forceToSearchBoth ? "" : "/or") + " graveyard for a card named " + filter.getMessage()
+ ", reveal it, and put it into your hand. " + (forceToSearchBoth ? "Then shuffle your library" : "If you search your library this way, shuffle it");
}
public SearchLibraryGraveyardPutOntoBattlefieldEffect(final SearchLibraryGraveyardPutOntoBattlefieldEffect effect) {
super(effect);
this.filter = effect.filter;
this.forceToSearchBoth = effect.forceToSearchBoth;
}
@Override
public SearchLibraryGraveyardPutOntoBattlefieldEffect copy() {
return new SearchLibraryGraveyardPutOntoBattlefieldEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
MageObject sourceObject = source.getSourceObject(game);
Card cardFound = null;
if (controller != null && sourceObject != null) {
if (forceToSearchBoth || controller.chooseUse(outcome, "Search your library for a card named " + filter.getMessage() + '?', source, game)) {
TargetCardInLibrary target = new TargetCardInLibrary(0, 1, filter);
target.clearChosen();
if (controller.searchLibrary(target, source, game)) {
if (!target.getTargets().isEmpty()) {
cardFound = game.getCard(target.getFirstTarget());
}
}
controller.shuffleLibrary(source, game);
}
if (cardFound == null && controller.chooseUse(outcome, "Search your graveyard for a card named " + filter.getMessage() + '?', source, game)) {
TargetCard target = new TargetCard(0, 1, Zone.GRAVEYARD, filter);
target.clearChosen();
if (controller.choose(outcome, controller.getGraveyard(), target, game)) {
if (!target.getTargets().isEmpty()) {
cardFound = game.getCard(target.getFirstTarget());
}
}
}
if (cardFound != null) {
controller.revealCards(sourceObject.getIdName(), new CardsImpl(cardFound), game);
controller.moveCards(cardFound, Zone.BATTLEFIELD, source, game);
}
return true;
}
return false;
}
}

View file

@ -0,0 +1,87 @@
package mage.abilities.effects.common.search;
import mage.MageObject;
import mage.abilities.Ability;
import mage.constants.ComparisonType;
import mage.abilities.effects.OneShotEffect;
import mage.cards.Card;
import mage.cards.CardsImpl;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.filter.FilterCard;
import mage.filter.predicate.mageobject.ConvertedManaCostPredicate;
import mage.game.Game;
import mage.players.Player;
import mage.target.TargetCard;
import mage.target.common.TargetCardInLibrary;
/**
*
* @author antoni-g
*/
public class SearchLibraryGraveyardWithLessCMCPutIntoPlay extends OneShotEffect {
private final FilterCard filter;
public SearchLibraryGraveyardWithLessCMCPutIntoPlay() {
this(new FilterCard());
}
public SearchLibraryGraveyardWithLessCMCPutIntoPlay(FilterCard filter) {
super(Outcome.PutCreatureInPlay);
this.filter = filter;
staticText = "Search your library or graveyard for a " + filter.getMessage() + " with converted mana cost X or less, put it onto the battlefield, then shuffle your library";
}
public SearchLibraryGraveyardWithLessCMCPutIntoPlay(final SearchLibraryGraveyardWithLessCMCPutIntoPlay effect) {
super(effect);
this.filter = effect.filter;
}
@Override
public SearchLibraryGraveyardWithLessCMCPutIntoPlay copy() {
return new SearchLibraryGraveyardWithLessCMCPutIntoPlay(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
MageObject sourceObject = source.getSourceObject(game);
Card cardFound = null;
if (controller != null && sourceObject != null) {
// create x cost filter
FilterCard advancedFilter = filter.copy(); // never change static objects so copy the object here before
advancedFilter.add(new ConvertedManaCostPredicate(ComparisonType.FEWER_THAN, source.getManaCostsToPay().getX() + 1));
if (controller.chooseUse(outcome, "Search your library for a " + filter.getMessage() + " with CMC X or less" + '?', source, game)) {
TargetCardInLibrary target = new TargetCardInLibrary(advancedFilter);
target.clearChosen();
if (controller.searchLibrary(target, source, game)) {
if (!target.getTargets().isEmpty()) {
cardFound = game.getCard(target.getFirstTarget());
}
}
controller.shuffleLibrary(source, game);
}
if (cardFound == null && controller.chooseUse(outcome, "Search your graveyard for a " + filter.getMessage() + " with CMC X or less" + '?', source, game)) {
TargetCard target = new TargetCard(0, 1, Zone.GRAVEYARD, advancedFilter);
target.clearChosen();
if (controller.choose(outcome, controller.getGraveyard(), target, game)) {
if (!target.getTargets().isEmpty()) {
cardFound = game.getCard(target.getFirstTarget());
}
}
}
if (cardFound != null) {
controller.revealCards(sourceObject.getIdName(), new CardsImpl(cardFound), game);
controller.moveCards(cardFound, Zone.BATTLEFIELD, source, game);
}
return true;
}
return false;
}
}

View file

@ -63,7 +63,7 @@ public class SearchLibraryPutInHandEffect extends SearchEffect {
return false;
}
target.clearChosen();
if (controller.searchLibrary(target, game)) {
if (controller.searchLibrary(target, source, game)) {
if (!target.getTargets().isEmpty()) {
Cards cards = new CardsImpl();
for (UUID cardId : target.getTargets()) {

View file

@ -66,7 +66,7 @@ public class SearchLibraryPutInHandOrOnBattlefieldEffect extends SearchEffect {
return false;
}
target.clearChosen();
if (controller.searchLibrary(target, game)) {
if (controller.searchLibrary(target, source, game)) {
if (!target.getTargets().isEmpty()) {
Cards cards = new CardsImpl();
boolean askToPutOntoBf = false;

View file

@ -62,7 +62,7 @@ public class SearchLibraryPutInPlayEffect extends SearchEffect {
if (player == null) {
return false;
}
if (player.searchLibrary(target, game)) {
if (player.searchLibrary(target, source, game)) {
if (!target.getTargets().isEmpty()) {
player.moveCards(new CardsImpl(target.getTargets()).getCards(game),
Zone.BATTLEFIELD, source, game, tapped, false, false, null);

View file

@ -70,7 +70,7 @@ public class SearchLibraryPutInPlayTargetPlayerEffect extends SearchEffect {
public boolean apply(Game game, Ability source) {
Player player = game.getPlayer(getTargetPointer().getFirst(game, source));
if (player != null) {
if (player.searchLibrary(target, game)) {
if (player.searchLibrary(target, source, game)) {
if (!target.getTargets().isEmpty()) {
player.moveCards(new CardsImpl(target.getTargets()).getCards(game),
Zone.BATTLEFIELD, source, game, tapped, false, ownerIsController, null);

View file

@ -50,7 +50,7 @@ public class SearchLibraryPutOnLibraryEffect extends SearchEffect {
if (controller == null || sourceObject == null) {
return false;
}
if (controller.searchLibrary(target, game)) {
if (controller.searchLibrary(target, source, game)) {
Cards foundCards = new CardsImpl(target.getTargets());
if (reveal && !foundCards.isEmpty()) {
controller.revealCards(sourceObject.getIdName(), foundCards, game);

View file

@ -43,7 +43,7 @@ public class SearchLibraryWithLessCMCPutInPlayEffect extends OneShotEffect {
FilterCard advancedFilter = filter.copy(); // never change static objects so copy the object here before
advancedFilter.add(new ConvertedManaCostPredicate(ComparisonType.FEWER_THAN, source.getManaCostsToPay().getX() + 1));
TargetCardInLibrary target = new TargetCardInLibrary(advancedFilter);
if (controller.searchLibrary(target, game)) {
if (controller.searchLibrary(target, source, game)) {
if (!target.getTargets().isEmpty()) {
Card card = controller.getLibrary().getCard(target.getFirstTarget(), game);
if (card != null) {

View file

@ -1,8 +1,10 @@
package mage.abilities.effects.keyword;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.counters.CounterType;
import mage.game.Game;
import mage.game.events.GameEvent;
@ -19,10 +21,10 @@ public class AdaptEffect extends OneShotEffect {
public AdaptEffect(int adaptNumber) {
super(Outcome.BoostCreature);
this.adaptNumber = adaptNumber;
staticText = "Adapt " + adaptNumber +
" <i>(If this creature has no +1/+1 counters on it, put " +
CardUtil.numberToText(adaptNumber) + " +1/+1 counter" +
(adaptNumber > 1 ? "s" : "") + " on it.)</i>";
staticText = "Adapt " + adaptNumber
+ " <i>(If this creature has no +1/+1 counters on it, put "
+ CardUtil.numberToText(adaptNumber) + " +1/+1 counter"
+ (adaptNumber > 1 ? "s" : "") + " on it.)</i>";
}
private AdaptEffect(final AdaptEffect effect) {
@ -37,7 +39,15 @@ public class AdaptEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
Permanent permanent = game.getPermanent(source.getSourceId());
// Verify source object did not change zone and is on the battlefield
MageObject sourceObject = source.getSourceObjectIfItStillExists(game);
if (sourceObject == null) {
if (game.getState().getZone(source.getSourceId()).equals(Zone.BATTLEFIELD)
&& source.getSourceObjectZoneChangeCounter() + 1 == game.getState().getZoneChangeCounter(source.getSourceId())) {
sourceObject = game.getPermanent(source.getSourceId());
}
}
Permanent permanent = ((Permanent) sourceObject);
if (permanent == null) {
return false;
}
@ -48,7 +58,8 @@ public class AdaptEffect extends OneShotEffect {
if (game.replaceEvent(event)) {
return false;
}
if (permanent.getCounters(game).getCount(CounterType.P1P1) == 0 || event.getFlag()) {
if (permanent.getCounters(game).getCount(CounterType.P1P1) == 0
|| event.getFlag()) {
permanent.addCounters(CounterType.P1P1.createInstance(event.getAmount()), source, game);
}
return true;

View file

@ -38,7 +38,7 @@ public class AmassEffect extends OneShotEffect {
public AmassEffect(int amassNumber) {
this(new StaticValue(amassNumber));
staticText = "amass " + amassNumber + ". <i>(Put " + CardUtil.numberToText(amassNumber)
+ " +1/+1 counter " + (amassNumber > 1 ? "s" : "")
+ " +1/+1 counter" + (amassNumber > 1 ? "s " : " ")
+ "on an Army you control. If you dont control one, "
+ "create a 0/0 black Zombie Army creature token first.)</i>";
}
@ -68,7 +68,7 @@ public class AmassEffect extends OneShotEffect {
if (player == null) {
return false;
}
if (!game.getBattlefield().contains(filter, 1, game)) {
if (!game.getBattlefield().contains(filter, source.getControllerId(), 1, game)) {
new CreateTokenEffect(new ZombieArmyToken()).apply(game, source);
}
Target target = new TargetPermanent(filter);

View file

@ -0,0 +1,26 @@
package mage.abilities.hint.common;
import mage.abilities.Ability;
import mage.abilities.dynamicvalue.common.PermanentsYouControlCount;
import mage.abilities.hint.Hint;
import mage.abilities.hint.ValueHint;
import mage.game.Game;
/**
* @author JayDi85
*/
public enum PermanentsYouControlHint implements Hint {
instance;
private static final Hint hint = new ValueHint("Permanents you control", PermanentsYouControlCount.instance);
@Override
public String getText(Game game, Ability ability) {
return hint.getText(game, ability);
}
@Override
public Hint copy() {
return instance;
}
}

View file

@ -1,7 +1,5 @@
package mage.abilities.keyword;
import java.util.List;
import mage.Mana;
import mage.abilities.Ability;
import mage.abilities.SpecialAction;
@ -24,6 +22,8 @@ import mage.players.Player;
import mage.target.common.TargetCardInYourGraveyard;
import mage.util.CardUtil;
import java.util.List;
/**
* 702.65. Delve 702.65a Delve is a static ability that functions while the
* spell with delve is on the stack. Delve means For each generic mana in
@ -31,7 +31,7 @@ import mage.util.CardUtil;
* pay that mana. The delve ability isn't an additional or alternative cost and
* applies only after the total cost of the spell with delve is determined.
* 702.65b Multiple instances of delve on the same spell are redundant.
*
* <p>
* The rules for delve have changed slightly since it was last in an expansion.
* Previously, delve reduced the cost to cast a spell. Under the current rules,
* you exile cards from your graveyard at the same time you pay the spell's
@ -45,7 +45,7 @@ import mage.util.CardUtil;
* it can be used in conjunction with alternative costs.
*
* @author LevelX2
*
* <p>
* TODO: Change card exiling to a way to pay mana costs, now it's maybe not
* possible to pay costs from effects that increase the mana costs.
*/
@ -83,7 +83,8 @@ public class DelveAbility extends SimpleStaticAbility implements AlternateManaPa
unpaidAmount = 1;
}
specialAction.addCost(new ExileFromGraveCost(new TargetCardInYourGraveyard(
0, Math.min(controller.getGraveyard().size(), unpaidAmount), new FilterCard(), true)));
0, Math.min(controller.getGraveyard().size(), unpaidAmount),
new FilterCard("cards to exile for delve's pay from your graveyard"), true)));
if (specialAction.canActivate(source.getControllerId(), game).canActivate()) {
game.getState().getSpecialActions().add(specialAction);
}

View file

@ -116,7 +116,7 @@ class PartnersWithSearchEffect extends OneShotEffect {
filter.add(new NamePredicate(partnerName));
TargetCardInLibrary target = new TargetCardInLibrary(filter);
if (player.chooseUse(Outcome.Benefit, "Search your library for a card named " + partnerName + " and put it into your hand?", source, game)) {
player.searchLibrary(target, game);
player.searchLibrary(target, source, game);
for (UUID cardId : target.getTargets()) {
Card card = player.getLibrary().getCard(cardId, game);
if (card != null) {

View file

@ -80,7 +80,7 @@ class TransmuteEffect extends OneShotEffect {
FilterCard filter = new FilterCard("card with converted mana cost " + sourceObject.getConvertedManaCost());
filter.add(new ConvertedManaCostPredicate(ComparisonType.EQUAL_TO, sourceObject.getConvertedManaCost()));
TargetCardInLibrary target = new TargetCardInLibrary(1, filter);
if (controller.searchLibrary(target, game)) {
if (controller.searchLibrary(target, source, game)) {
if (!target.getTargets().isEmpty()) {
Cards revealed = new CardsImpl(target.getTargets());
controller.revealCards(sourceObject.getIdName(), revealed, game);

View file

@ -1,4 +1,3 @@
package mage.abilities.keyword;
import mage.abilities.Ability;
@ -19,19 +18,17 @@ import mage.game.events.GameEvent.EventType;
import mage.game.events.ZoneChangeEvent;
/**
*
* @author BetaSteward_at_googlemail.com
*
*
* <p>
* <p>
* 702.82. Unearth
*
* <p>
* 702.82a Unearth is an activated ability that functions while the card with
* unearth is in a graveyard. "Unearth [cost]" means "[Cost]: Return this card
* from your graveyard to the battlefield. It gains haste. Exile it at the
* beginning of the next end step. If it would leave the battlefield, exile it
* instead of putting it anywhere else. Activate this ability only any time you
* could cast a sorcery."
*
*/
public class UnearthAbility extends ActivatedAbilityImpl {
@ -111,7 +108,7 @@ class UnearthLeavesBattlefieldEffect extends ReplacementEffectImpl {
@Override
public boolean checksEventType(GameEvent event, Game game) {
return EventType.ZONE_CHANGE == event.getType();
return event.getType() == EventType.ZONE_CHANGE;
}
@Override

View file

@ -690,27 +690,33 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
sourceId = source.getSourceId();
}
}
GameEvent countersEvent = GameEvent.getEvent(GameEvent.EventType.ADD_COUNTERS, objectId, sourceId, getControllerOrOwner(), counter.getName(), counter.getCount());
countersEvent.setAppliedEffects(appliedEffects);
countersEvent.setFlag(isEffect);
if (!game.replaceEvent(countersEvent)) {
int amount = countersEvent.getAmount();
GameEvent addingAllEvent = GameEvent.getEvent(GameEvent.EventType.ADD_COUNTERS, objectId, sourceId, getControllerOrOwner(), counter.getName(), counter.getCount());
addingAllEvent.setAppliedEffects(appliedEffects);
addingAllEvent.setFlag(isEffect);
if (!game.replaceEvent(addingAllEvent)) {
int amount = addingAllEvent.getAmount();
boolean isEffectFlag = addingAllEvent.getFlag();
int finalAmount = amount;
for (int i = 0; i < amount; i++) {
Counter eventCounter = counter.copy();
eventCounter.remove(eventCounter.getCount() - 1);
GameEvent event = GameEvent.getEvent(GameEvent.EventType.ADD_COUNTER, objectId, sourceId, getControllerOrOwner(), counter.getName(), 1);
event.setAppliedEffects(appliedEffects);
if (!game.replaceEvent(event)) {
GameEvent addingOneEvent = GameEvent.getEvent(GameEvent.EventType.ADD_COUNTER, objectId, sourceId, getControllerOrOwner(), counter.getName(), 1);
addingOneEvent.setAppliedEffects(appliedEffects);
addingOneEvent.setFlag(isEffectFlag);
if (!game.replaceEvent(addingOneEvent)) {
getCounters(game).addCounter(eventCounter);
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.COUNTER_ADDED, objectId, sourceId, getControllerOrOwner(), counter.getName(), 1));
GameEvent addedOneEvent = GameEvent.getEvent(GameEvent.EventType.COUNTER_ADDED, objectId, sourceId, getControllerOrOwner(), counter.getName(), 1);
addedOneEvent.setFlag(addingOneEvent.getFlag());
game.fireEvent(addedOneEvent);
} else {
finalAmount--;
returnCode = false;
}
}
if (finalAmount > 0) {
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.COUNTERS_ADDED, objectId, sourceId, getControllerOrOwner(), counter.getName(), amount));
GameEvent addedAllEvent = GameEvent.getEvent(GameEvent.EventType.COUNTERS_ADDED, objectId, sourceId, getControllerOrOwner(), counter.getName(), amount);
addedAllEvent.setFlag(isEffectFlag);
game.fireEvent(addedAllEvent);
}
} else {
returnCode = false;
@ -720,6 +726,7 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
@Override
public void removeCounters(String name, int amount, Game game) {
int finalAmount = 0;
for (int i = 0; i < amount; i++) {
if (!getCounters(game).removeCounter(name, 1)) {
break;
@ -727,7 +734,12 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
GameEvent event = GameEvent.getEvent(GameEvent.EventType.COUNTER_REMOVED, objectId, getControllerOrOwner());
event.setData(name);
game.fireEvent(event);
finalAmount++;
}
GameEvent event = GameEvent.getEvent(GameEvent.EventType.COUNTERS_REMOVED, objectId, getControllerOrOwner());
event.setData(name);
event.setAmount(finalAmount);
game.fireEvent(event);
}
@Override

View file

@ -258,7 +258,7 @@ public abstract class ExpansionSet implements Serializable {
return booster.stream().anyMatch(card -> card.isLegendary() && card.isCreature());
}
if (needsPlaneswalker) {
return booster.stream().anyMatch(card -> card.isPlaneswalker());
return booster.stream().filter(card -> card.isPlaneswalker()).count() == 1;
}
// TODO: add partner check

View file

@ -1,5 +1,6 @@
package mage.cards.decks.exporter;
import com.google.common.collect.ImmutableMap;
import mage.cards.decks.DeckCardInfo;
import mage.cards.decks.DeckCardLists;
import mage.cards.decks.DeckFileFilter;
@ -13,9 +14,11 @@ import java.util.*;
*/
public class MtgArenaDeckExporter extends DeckExporter {
private final String ext = "mtga";
private final String description = "MTG Arena's deck format (*.mtga)";
private final FileFilter fileFilter = new DeckFileFilter(ext, description);
private static final String ext = "mtga";
private static final String description = "MTG Arena's deck format (*.mtga)";
private static final FileFilter fileFilter = new DeckFileFilter(ext, description);
private static final Map<String, String> SET_CODE_REPLACEMENTS = ImmutableMap.of("DOM", "DAR");
@Override
public void writeDeck(PrintWriter out, DeckCardLists deck) {
@ -33,7 +36,9 @@ public class MtgArenaDeckExporter extends DeckExporter {
private List<String> prepareCardsList(List<DeckCardInfo> sourceCards, Map<String, Integer> amount, String prefix) {
List<String> res = new ArrayList<>();
for (DeckCardInfo card : sourceCards) {
String name = card.getCardName() + " (" + card.getSetCode().toUpperCase(Locale.ENGLISH) + ") " + card.getCardNum();
String setCode = card.getSetCode().toUpperCase(Locale.ENGLISH);
setCode = SET_CODE_REPLACEMENTS.getOrDefault(setCode, setCode);
String name = card.getCardName() + " (" + setCode + ") " + card.getCardNum();
String code = prefix + name;
int curAmount = amount.getOrDefault(code, 0);
if (curAmount == 0) {

View file

@ -22,6 +22,7 @@ public class MtgArenaDeckExporterTest {
deck.getCards().add(new DeckCardInfo("Plains", "2", "RNA", 3));
deck.getCards().add(new DeckCardInfo("Plains", "2", "RNA", 5)); // must combine
deck.getCards().add(new DeckCardInfo("Mountain", "3", "RNA", 1));
deck.getCards().add(new DeckCardInfo("Goblin Chainwhirler", "129", "DOM", 4));
deck.getSideboard().add(new DeckCardInfo("Island", "1", "RNA", 2));
deck.getSideboard().add(new DeckCardInfo("Island", "1", "RNA", 5)); // must combine
deck.getSideboard().add(new DeckCardInfo("Mountain", "2", "RNA", 3));
@ -30,6 +31,7 @@ public class MtgArenaDeckExporterTest {
assertEquals("2 Forest (RNA) 1" + System.lineSeparator() +
"8 Plains (RNA) 2" + System.lineSeparator() +
"1 Mountain (RNA) 3" + System.lineSeparator() +
"4 Goblin Chainwhirler (DAR) 129" + System.lineSeparator() +
System.lineSeparator() +
"7 Island (RNA) 1" + System.lineSeparator() +
"3 Mountain (RNA) 2" + System.lineSeparator(),

View file

@ -34,6 +34,8 @@ public abstract class DeckImporter {
return new CodDeckImporter();
} else if (file.toLowerCase(Locale.ENGLISH).endsWith("o8d")) {
return new O8dDeckImporter();
} else if (file.toLowerCase(Locale.ENGLISH).endsWith("draft")) {
return new DraftLogImporter();
} else {
return null;
}

View file

@ -0,0 +1,46 @@
package mage.cards.decks.importer;
import mage.cards.decks.DeckCardInfo;
import mage.cards.decks.DeckCardLists;
import mage.cards.repository.CardCriteria;
import mage.cards.repository.CardInfo;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class DraftLogImporter extends PlainTextDeckImporter {
private static Pattern SET_PATTERN = Pattern.compile("------ (\\p{Alnum}+) ------$");
private static Pattern PICK_PATTERN = Pattern.compile("--> (.+)$");
private String currentSet = null;
@Override
protected void readLine(String line, DeckCardLists deckList) {
Matcher setMatcher = SET_PATTERN.matcher(line);
if (setMatcher.matches()) {
currentSet = setMatcher.group(1);
return;
}
Matcher pickMatcher = PICK_PATTERN.matcher(line);
if (pickMatcher.matches()) {
String name = pickMatcher.group(1);
List<CardInfo> cards = getCardLookup().lookupCardInfo(new CardCriteria().setCodes(currentSet).name(name));
CardInfo card = null;
if (!cards.isEmpty()) {
card = cards.get(0);
} else {
card = getCardLookup().lookupCardInfo(name).orElse(null);
}
if (card != null) {
deckList.getCards().add(new DeckCardInfo(card.getName(), card.getCardNumber(), card.getSetCode()));
} else {
sbMessage.append("couldn't find: \"").append(name).append("\"\n");
}
}
}
}

View file

@ -79,8 +79,36 @@ public class TxtDeckImporter extends PlainTextDeckImporter {
if (delim < 0) {
return;
}
String lineNum = line.substring(0, delim).trim();
String lineName = line.substring(delim).replace("'", "\'").trim();
if (IGNORE_NAMES.contains(lineNum)) {
return;
}
// amount
int cardAmount = 0;
boolean haveCardAmout;
try {
cardAmount = Integer.parseInt(lineNum.replaceAll("\\D+", ""));
if ((cardAmount <= 0) || (cardAmount >= 100)) {
sbMessage.append("Invalid number (too small or too big): ").append(lineNum).append(" at line ").append(lineCount).append('\n');
return;
}
haveCardAmout = true;
} catch (NumberFormatException nfe) {
haveCardAmout = false;
//sbMessage.append("Invalid number: ").append(lineNum).append(" at line ").append(lineCount).append('\n');
//return;
}
String lineName;
if (haveCardAmout) {
lineName = line.substring(delim).trim();
} else {
lineName = line.trim();
cardAmount = 1;
}
lineName = lineName
.replace("&amp;", "//")
.replace("Æ", "Ae")
@ -96,33 +124,23 @@ public class TxtDeckImporter extends PlainTextDeckImporter {
}
lineName = lineName.replaceFirst("(?<=[^/])\\s*/\\s*(?=[^/])", " // ");
if (IGNORE_NAMES.contains(lineName) || IGNORE_NAMES.contains(lineNum)) {
if (IGNORE_NAMES.contains(lineName)) {
return;
}
wasCardLines = true;
try {
int num = Integer.parseInt(lineNum.replaceAll("\\D+", ""));
if ((num < 0) || (num > 100)) {
sbMessage.append("Invalid number (too small or too big): ").append(lineNum).append(" at line ").append(lineCount).append('\n');
return;
}
CardInfo cardInfo = CardRepository.instance.findPreferedCoreExpansionCard(lineName, true);
if (cardInfo == null) {
sbMessage.append("Could not find card: '").append(lineName).append("' at line ").append(lineCount).append('\n');
} else {
for (int i = 0; i < num; i++) {
if (!sideboard && !singleLineSideBoard) {
deckList.getCards().add(new DeckCardInfo(cardInfo.getName(), cardInfo.getCardNumber(), cardInfo.getSetCode()));
} else {
deckList.getSideboard().add(new DeckCardInfo(cardInfo.getName(), cardInfo.getCardNumber(), cardInfo.getSetCode()));
}
CardInfo cardInfo = CardRepository.instance.findPreferedCoreExpansionCard(lineName, true);
if (cardInfo == null) {
sbMessage.append("Could not find card: '").append(lineName).append("' at line ").append(lineCount).append('\n');
} else {
for (int i = 0; i < cardAmount; i++) {
if (!sideboard && !singleLineSideBoard) {
deckList.getCards().add(new DeckCardInfo(cardInfo.getName(), cardInfo.getCardNumber(), cardInfo.getSetCode()));
} else {
deckList.getSideboard().add(new DeckCardInfo(cardInfo.getName(), cardInfo.getCardNumber(), cardInfo.getSetCode()));
}
}
} catch (NumberFormatException nfe) {
sbMessage.append("Invalid number: ").append(lineNum).append(" at line ").append(lineCount).append('\n');
}
}
}

View file

@ -35,7 +35,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 = 220;
private static final long CARD_CONTENT_VERSION = 222;
private Dao<CardInfo, Object> cardDao;
private Set<String> classNames;
private RepositoryEventSource eventSource = new RepositoryEventSource();

View file

@ -1,4 +1,3 @@
package mage.choices;
import mage.MageObject;
@ -8,14 +7,13 @@ import mage.ObjectColor;
import java.util.ArrayList;
/**
*
* @author BetaSteward_at_googlemail.com, JayDi85
*/
public class ChoiceColor extends ChoiceImpl {
private static final ArrayList<String> colorChoices = getBaseColors();
private static final ArrayList<String> colorChoices = getBaseColors();
public static ArrayList<String> getBaseColors(){
public static ArrayList<String> getBaseColors() {
ArrayList<String> arr = new ArrayList<>();
arr.add("Green");
arr.add("Blue");
@ -33,15 +31,15 @@ public class ChoiceColor extends ChoiceImpl {
this(required, "Choose color");
}
public ChoiceColor(boolean required, String chooseMessage){
public ChoiceColor(boolean required, String chooseMessage) {
this(required, chooseMessage, "");
}
public ChoiceColor(boolean required, String chooseMessage, MageObject source){
public ChoiceColor(boolean required, String chooseMessage, MageObject source) {
this(required, chooseMessage, source.getIdName());
}
public ChoiceColor(boolean required, String chooseMessage, String chooseSubMessage){
public ChoiceColor(boolean required, String chooseMessage, String chooseSubMessage) {
super(required);
this.choices.addAll(colorChoices);
@ -59,6 +57,10 @@ public class ChoiceColor extends ChoiceImpl {
return new ChoiceColor(this);
}
public void removeColorFromChoices(String colorName) {
this.choices.remove(colorName);
}
public ObjectColor getColor() {
if (choice == null) {
return null;

View file

@ -31,6 +31,16 @@ public enum CardType {
return text;
}
public static CardType fromString(String value) {
for (CardType ct : CardType.values()) {
if (ct.toString().equals(value)) {
return ct;
}
}
throw new IllegalArgumentException("Can't find card type enum value: " + value);
}
public boolean isPermanentType() {
return permanentType;
}

View file

@ -1,7 +1,6 @@
package mage.constants;
/**
*
* @author North
*/
public enum Duration {
@ -12,6 +11,7 @@ public enum Duration {
WhileInGraveyard("", false),
EndOfTurn("until end of turn", true),
UntilYourNextTurn("until your next turn", true),
UntilEndOfYourNextTurn("until the end of your next turn", true),
UntilSourceLeavesBattlefield("until {source} leaves the battlefield", true), // supported for continuous layered effects
EndOfCombat("until end of combat", true),
EndOfStep("until end of phase step", true),

View file

@ -201,6 +201,7 @@ public enum SubType {
KOR("Kor", SubTypeSet.CreatureType),
KRAKEN("Kraken", SubTypeSet.CreatureType),
// L
LADYOFPROPERETIQUETTE("Lady of Proper Etiquette", SubTypeSet.CreatureType, true), // Unglued
LAMIA("Lamia", SubTypeSet.CreatureType),
LAMMASU("Lammasu", SubTypeSet.CreatureType),
LEECH("Leech", SubTypeSet.CreatureType),
@ -392,6 +393,7 @@ public enum SubType {
HUATLI("Huatli", SubTypeSet.PlaneswalkerType),
JACE("Jace", SubTypeSet.PlaneswalkerType),
KARN("Karn", SubTypeSet.PlaneswalkerType),
KASMINA("Kasmina", SubTypeSet.PlaneswalkerType),
KAYA("Kaya", SubTypeSet.PlaneswalkerType),
KIORA("Kiora", SubTypeSet.PlaneswalkerType),
KOTH("Koth", SubTypeSet.PlaneswalkerType),

View file

@ -135,6 +135,7 @@ public enum CounterType {
VERSE("verse"),
VITALITY("vitality"),
VORTEX("vortex"),
WAGE("wage"),
WINCH("winch"),
WIND("wind"),
WISH("wish");
@ -193,6 +194,11 @@ public enum CounterType {
}
}
@Override
public String toString() {
return name;
}
public static CounterType findByName(String name) {
for (CounterType counterType : values()) {
if (counterType.getName().equals(name)) {

View file

@ -417,6 +417,12 @@ public final class StaticFilters {
FILTER_PERMANENT_PLANESWALKER.setLockedFilter(true);
}
public static final FilterPlaneswalkerPermanent FILTER_PERMANENT_PLANESWALKERS = new FilterPlaneswalkerPermanent("planeswalkers");
static {
FILTER_PERMANENT_PLANESWALKERS.setLockedFilter(true);
}
public static final FilterNonlandPermanent FILTER_PERMANENT_NON_LAND = new FilterNonlandPermanent();
static {
@ -460,6 +466,12 @@ public final class StaticFilters {
FILTER_SPELL_NON_CREATURE.setLockedFilter(true);
}
public static final FilterSpell FILTER_SPELL_A_NON_CREATURE = (FilterSpell) new FilterSpell("a noncreature spell").add(Predicates.not(new CardTypePredicate(CardType.CREATURE)));
static {
FILTER_SPELL_A_NON_CREATURE.setLockedFilter(true);
}
public static final FilterSpell FILTER_SPELL = new FilterSpell();
static {

View file

@ -1,13 +1,11 @@
package mage.filter.common;
import mage.MageItem;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
import java.util.UUID;
import mage.MageItem;
/**
* @author nantuko
@ -28,24 +26,24 @@ public class FilterPermanentOrPlayerWithCounter extends FilterPermanentOrPlayer
@Override
public boolean match(MageItem o, Game game) {
if (o instanceof Player) {
if (((Player)o).getCounters().isEmpty()) {
return false;
}
} else if (o instanceof Permanent) {
if (((Permanent)o).getCounters(game).isEmpty()) {
return false;
if (super.match(o, game)) {
if (o instanceof Player) {
return !((Player) o).getCounters().isEmpty();
} else if (o instanceof Permanent) {
return !((Permanent) o).getCounters(game).isEmpty();
}
}
return super.match(o, game);
return false;
}
@Override
public boolean match(MageItem o, UUID sourceId, UUID playerId, Game game) {
if (o instanceof Player) {
return playerFilter.match((Player) o, sourceId, playerId, game);
} else if (o instanceof Permanent) {
return permanentFilter.match((Permanent) o, sourceId, playerId, game);
if (super.match(o, sourceId, playerId, game)) {
if (o instanceof Player) {
return !((Player) o).getCounters().isEmpty();
} else if (o instanceof Permanent) {
return !((Permanent) o).getCounters(game).isEmpty();
}
}
return false;
}

View file

@ -3,11 +3,12 @@
package mage.filter.common;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.filter.FilterPermanent;
import mage.filter.predicate.mageobject.CardTypePredicate;
import mage.filter.predicate.mageobject.SubtypePredicate;
/**
*
* @author BetaSteward_at_googlemail.com
*/
public class FilterPlaneswalkerPermanent extends FilterPermanent {
@ -16,6 +17,11 @@ public class FilterPlaneswalkerPermanent extends FilterPermanent {
this("planeswalker");
}
public FilterPlaneswalkerPermanent(SubType subType) {
this(subType.getDescription() + " planeswalker");
this.add(new SubtypePredicate(subType));
}
public FilterPlaneswalkerPermanent(String name) {
super(name);
this.add(new CardTypePredicate(CardType.PLANESWALKER));

View file

@ -1,22 +1,15 @@
package mage.game;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.UUID;
import java.util.stream.Collectors;
import mage.cards.Card;
import mage.filter.FilterCard;
import mage.util.Copyable;
import java.io.Serializable;
import java.util.*;
import java.util.Map.Entry;
import java.util.stream.Collectors;
/**
*
* @author BetaSteward_at_googlemail.com
*/
public class Exile implements Serializable, Copyable<Exile> {
@ -114,4 +107,17 @@ public class Exile implements Serializable, Copyable<Exile> {
exile.clear();
}
}
public void cleanupEndOfTurnZones(Game game) {
// moves cards from outdated zone to main exile zone
ExileZone mainZone = getExileZone(PERMANENT);
for (ExileZone zone : exileZones.values()) {
if (zone.isCleanupOnEndTurn()) {
for (Card card : zone.getCards(game)) {
mainZone.add(card);
zone.remove(card);
}
}
}
}
}

View file

@ -1,13 +1,10 @@
package mage.game;
import java.util.UUID;
import mage.cards.CardsImpl;
import java.util.UUID;
/**
*
* @author BetaSteward_at_googlemail.com
*/
public class ExileZone extends CardsImpl {
@ -15,16 +12,22 @@ public class ExileZone extends CardsImpl {
private UUID id;
private String name;
private boolean hidden;
private boolean cleanupOnEndTurn = false; // moved cards from that zone to default on end of turn (to cleanup exile windows)
public ExileZone(UUID id, String name) {
this(id, name, false);
}
public ExileZone(UUID id, String name, boolean hidden) {
this(id, name, false, false);
}
public ExileZone(UUID id, String name, boolean hidden, boolean cleanupOnEndTurn) {
super();
this.id = id;
this.name = name;
this.hidden = hidden;
this.cleanupOnEndTurn = cleanupOnEndTurn;
}
public ExileZone(final ExileZone zone) {
@ -32,6 +35,7 @@ public class ExileZone extends CardsImpl {
this.id = zone.id;
this.name = zone.name;
this.hidden = zone.hidden;
this.cleanupOnEndTurn = zone.cleanupOnEndTurn;
}
public UUID getId() {
@ -46,6 +50,14 @@ public class ExileZone extends CardsImpl {
return hidden;
}
public boolean isCleanupOnEndTurn() {
return cleanupOnEndTurn;
}
public void setCleanupOnEndTurn(boolean cleanupOnEndTurn) {
this.cleanupOnEndTurn = cleanupOnEndTurn;
}
@Override
public ExileZone copy() {
return new ExileZone(this);

View file

@ -25,7 +25,6 @@ import mage.game.events.GameEvent;
import mage.game.events.Listener;
import mage.game.events.PlayerQueryEvent;
import mage.game.events.TableEvent;
import mage.game.match.Match;
import mage.game.match.MatchType;
import mage.game.mulligan.Mulligan;
import mage.game.permanent.Battlefield;
@ -134,7 +133,7 @@ public interface Game extends MageItem, Serializable {
default boolean isActivePlayer(UUID playerId) {
return getActivePlayerId().equals(playerId);
return getActivePlayerId() != null && getActivePlayerId().equals(playerId);
}
/**
@ -433,7 +432,7 @@ public interface Game extends MageItem, Serializable {
// game cheats (for tests only)
void cheat(UUID ownerId, Map<Zone, String> commands);
void cheat(UUID ownerId, List<Card> library, List<Card> hand, List<PermanentCard> battlefield, List<Card> graveyard);
void cheat(UUID ownerId, UUID activePlayerId, List<Card> library, List<Card> hand, List<PermanentCard> battlefield, List<Card> graveyard, List<Card> command);
// controlling the behaviour of replacement effects while permanents entering the battlefield
void setScopeRelevant(boolean scopeRelevant);

View file

@ -40,7 +40,6 @@ public abstract class GameCommanderImpl extends GameImpl {
@Override
protected void init(UUID choosingPlayerId) {
Ability ability = new SimpleStaticAbility(Zone.COMMAND, new InfoEffect("Commander effects"));
//Move commander to command zone
for (UUID playerId : state.getPlayerList(startingPlayerId)) {
Player player = getPlayer(playerId);
@ -49,7 +48,7 @@ public abstract class GameCommanderImpl extends GameImpl {
for (UUID commanderId : player.getCommandersIds()) {
Card commander = this.getCard(commanderId);
if (commander != null) {
initCommander(commander, ability, player);
initCommander(commander, player);
}
}
} else {
@ -57,20 +56,20 @@ public abstract class GameCommanderImpl extends GameImpl {
Card commander = this.getCard(player.getSideboard().iterator().next());
if (commander != null) {
player.addCommanderId(commander.getId());
initCommander(commander, ability, player);
initCommander(commander, player);
}
}
}
}
}
this.getState().addAbility(ability, null);
super.init(choosingPlayerId);
if (startingPlayerSkipsDraw) {
state.getTurnMods().add(new TurnMod(startingPlayerId, PhaseStep.DRAW));
}
}
private void initCommander(Card commander, Ability ability, Player player) {
public void initCommander(Card commander, Player player) {
Ability ability = new SimpleStaticAbility(Zone.COMMAND, new InfoEffect("Commander effects"));
commander.moveToZone(Zone.COMMAND, null, this, true);
commander.getAbilities().setControllerId(player.getId());
ability.addEffect(new CommanderReplacementEffect(commander.getId(), alsoHand, alsoLibrary));
@ -79,6 +78,7 @@ public abstract class GameCommanderImpl extends GameImpl {
CommanderInfoWatcher watcher = new CommanderInfoWatcher(commander.getId(), checkCommanderDamage);
getState().addWatcher(watcher);
watcher.addCardInfoToCommander(this);
this.getState().addAbility(ability, null);
}
//20130711

View file

@ -40,7 +40,6 @@ import mage.game.command.Emblem;
import mage.game.command.Plane;
import mage.game.events.*;
import mage.game.events.TableEvent.EventType;
import mage.game.mulligan.LondonMulligan;
import mage.game.mulligan.Mulligan;
import mage.game.permanent.Battlefield;
import mage.game.permanent.Permanent;
@ -128,7 +127,7 @@ public abstract class GameImpl implements Game, Serializable {
private int priorityTime;
private final int startLife;
protected PlayerList playerList;
protected PlayerList playerList; // auto-generated from state, don't copy
// infinite loop check (no copy of this attributes neccessary)
private int infiniteLoopCounter; // used to check if the game is in an infinite loop
@ -138,8 +137,9 @@ public abstract class GameImpl implements Game, Serializable {
// used to set the counters a permanent adds the battlefield (if no replacement effect is used e.g. Persist)
protected Map<UUID, Counters> enterWithCounters = new HashMap<>();
// used to proceed player conceding requests
private final LinkedList<UUID> concedingPlayers = new LinkedList<>(); // used to handle asynchronous request of a player to leave the game
// temporary store for income concede commands, don't copy
private final LinkedList<UUID> concedingPlayers = new LinkedList<>();
public GameImpl(MultiplayerAttackOption attackOption, RangeOfInfluence range, Mulligan mulligan, int startLife) {
this.id = UUID.randomUUID();
@ -2851,25 +2851,40 @@ public abstract class GameImpl implements Game, Serializable {
}
@Override
public void cheat(UUID ownerId, List<Card> library, List<Card> hand, List<PermanentCard> battlefield, List<Card> graveyard) {
public void cheat(UUID ownerId, UUID activePlayerId, List<Card> library, List<Card> hand, List<PermanentCard> battlefield, List<Card> graveyard, List<Card> command) {
Player player = getPlayer(ownerId);
if (player != null) {
loadCards(ownerId, library);
loadCards(ownerId, hand);
loadCards(ownerId, battlefield);
loadCards(ownerId, graveyard);
loadCards(ownerId, command);
for (Card card : library) {
player.getLibrary().putOnTop(card, this);
}
for (Card card : hand) {
card.setZone(Zone.HAND, this);
player.getHand().add(card);
}
for (Card card : graveyard) {
card.setZone(Zone.GRAVEYARD, this);
player.getGraveyard().add(card);
}
// as commander (only commander games, look at init code in GameCommanderImpl)
if (this instanceof GameCommanderImpl) {
for (Card card : command) {
player.addCommanderId(card.getId());
// no needs in initCommander call -- it's uses on game startup (init)
}
} else if (!command.isEmpty()) {
throw new IllegalArgumentException("Command zone supports in commander test games");
}
// warning, permanents go to battlefield without resolve, continuus effects must be init
for (PermanentCard permanentCard : battlefield) {
permanentCard.setZone(Zone.BATTLEFIELD, this);
permanentCard.setOwnerId(ownerId);
@ -2882,6 +2897,14 @@ public abstract class GameImpl implements Game, Serializable {
if (permanentCard.isTapped()) {
newPermanent.setTapped(true);
}
// init effects on static abilities (init continuous effects, warning, game state contains copy)
for (ContinuousEffect effect : this.getState().getContinuousEffects().getLayeredEffects(this)) {
Optional<Ability> ability = this.getState().getContinuousEffects().getLayeredEffectAbilities(effect).stream().findFirst();
if (ability.isPresent() && newPermanent.getId().equals(ability.get().getSourceId())) {
effect.init(ability.get(), this, activePlayerId); // game is not setup yet, game.activePlayer is null -- need direct id
}
}
}
applyEffects();
}

View file

@ -569,17 +569,20 @@ public class GameState implements Serializable, Copyable<GameState> {
combat.checkForRemoveFromCombat(game);
}
// Remove End of Combat effects
// remove end of combat effects
public void removeEocEffects(Game game) {
effects.removeEndOfCombatEffects();
delayed.removeEndOfCombatAbilities();
game.applyEffects();
}
// remove end of turn effects
public void removeEotEffects(Game game) {
effects.removeEndOfTurnEffects();
delayed.removeEndOfTurnAbilities();
effects.removeEndOfTurnEffects(game);
delayed.removeEndOfTurnAbilities(game);
exile.cleanupEndOfTurnZones(game);
game.applyEffects();
effects.incYourTurnNumPlayed(game);
}
public void addEffect(ContinuousEffect effect, Ability source) {
@ -787,7 +790,7 @@ public class GameState implements Serializable, Copyable<GameState> {
public void addCard(Card card) {
setZone(card.getId(), Zone.OUTSIDE);
for (Ability ability : card.getAbilities()) {
addAbility(ability, card);
addAbility(ability, null, card);
}
}

View file

@ -1,9 +1,5 @@
package mage.game;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.SimpleStaticAbility;
@ -21,8 +17,11 @@ import mage.game.turn.TurnMod;
import mage.players.Player;
import mage.watchers.common.CommanderInfoWatcher;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
/**
*
* @author JRHerlehy
*/
public abstract class GameTinyLeadersImpl extends GameImpl {
@ -43,7 +42,6 @@ public abstract class GameTinyLeadersImpl extends GameImpl {
@Override
protected void init(UUID choosingPlayerId) {
Ability ability = new SimpleStaticAbility(Zone.COMMAND, new InfoEffect("Commander effects"));
//Move tiny leader to command zone
for (UUID playerId : state.getPlayerList(startingPlayerId)) {
Player player = getPlayer(playerId);
@ -55,6 +53,7 @@ public abstract class GameTinyLeadersImpl extends GameImpl {
this.loadCards(cards, playerId);
player.addCommanderId(commander.getId());
commander.moveToZone(Zone.COMMAND, null, this, true);
Ability ability = new SimpleStaticAbility(Zone.COMMAND, new InfoEffect("Commander effects"));
ability.addEffect(new CommanderReplacementEffect(commander.getId(), alsoHand, alsoLibrary));
ability.addEffect(new CommanderCostModification(commander.getId()));
// Commander rule #4 was removed Jan. 18, 2016
@ -63,13 +62,13 @@ public abstract class GameTinyLeadersImpl extends GameImpl {
CommanderInfoWatcher watcher = new CommanderInfoWatcher(commander.getId(), false);
getState().addWatcher(watcher);
watcher.addCardInfoToCommander(this);
this.getState().addAbility(ability, null);
} else {
throw new UnknownError("Commander card could not be created. Name: [" + player.getMatchPlayer().getDeck().getName() + ']');
}
}
}
this.getState().addAbility(ability, null);
super.init(choosingPlayerId);
if (startingPlayerSkipsDraw) {
state.getTurnMods().add(new TurnMod(startingPlayerId, PhaseStep.DRAW));

View file

@ -461,18 +461,14 @@ public class Combat implements Serializable, Copyable<Combat> {
creaturesForcedToAttack.put(creature.getId(), defendersForcedToAttack);
// No need to attack a special defender
if (defendersForcedToAttack.isEmpty()) {
if (defenders.size() == 1) {
player.declareAttacker(creature.getId(), defenders.iterator().next(), game, false);
if (defendersCostlessAttackable.size() == 1) {
player.declareAttacker(creature.getId(), defendersCostlessAttackable.iterator().next(), game, false);
} else {
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)) {
player.declareAttacker(creature.getId(), target.getFirstTarget(), game, false);
}
TargetDefender target = new TargetDefender(defendersCostlessAttackable, creature.getId());
target.setRequired(true);
target.setTargetName("planeswalker or player for " + creature.getLogName() + " to attack (must attack effect)");
if (player.chooseTarget(Outcome.Damage, target, null, game)) {
player.declareAttacker(creature.getId(), target.getFirstTarget(), game, false);
}
}
} else {
@ -481,6 +477,7 @@ public class Combat implements Serializable, Copyable<Combat> {
} else {
TargetDefender target = new TargetDefender(defendersForcedToAttack, creature.getId());
target.setRequired(true);
target.setTargetName("planeswalker or player for " + creature.getLogName() + " to attack (must attack effect)");
if (player.chooseTarget(Outcome.Damage, target, null, game)) {
player.declareAttacker(creature.getId(), target.getFirstTarget(), game, false);
}
@ -555,7 +552,8 @@ public class Combat implements Serializable, Copyable<Combat> {
}
for (UUID attackingCreatureID : game.getCombat().getAttackers()) {
Permanent permanent = game.getPermanent(attackingCreatureID);
if (permanent != null && permanent.getBlocking() == 0) {
CombatGroup group = game.getCombat().findGroup(attackingCreatureID);
if (permanent != null && group != null && !group.getBlocked()) {
game.fireEvent(GameEvent.getEvent(EventType.UNBLOCKED_ATTACKER, attackingCreatureID, attackingPlayerId));
}
}
@ -1539,6 +1537,18 @@ public class Combat implements Serializable, Copyable<Combat> {
return false;
}
public boolean isPlaneswalkerAttacked(UUID defenderId, Game game) {
for (CombatGroup group : groups) {
if (group.defenderIsPlaneswalker) {
Permanent permanent = game.getPermanent(group.getDefenderId());
if (permanent.isControlledBy(defenderId)) {
return true;
}
}
}
return false;
}
/**
* @param attackerId
* @return uuid of defending player or planeswalker

View file

@ -14,7 +14,6 @@ import mage.filter.predicate.mageobject.CardTypePredicate;
import mage.game.command.Emblem;
/**
*
* @author spjspj
*/
public final class ElspethKnightErrantEmblem extends Emblem {
@ -28,7 +27,7 @@ public final class ElspethKnightErrantEmblem extends Emblem {
new CardTypePredicate(CardType.ENCHANTMENT),
new CardTypePredicate(CardType.LAND)));
Effect effect = new GainAbilityAllEffect(IndestructibleAbility.getInstance(), Duration.WhileOnBattlefield, filter, false);
effect.setText("Artifacts, creatures, enchantments, and lands you control are indestructible");
effect.setText("Artifacts, creatures, enchantments, and lands you control have indestructible");
this.getAbilities().add(new SimpleStaticAbility(Zone.COMMAND, effect));
this.setExpansionSetCodeForImage("MMA");
}

View file

@ -64,10 +64,12 @@ class JayaBallardCastFromGraveyardEffect extends AsThoughEffectImpl {
@Override
public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) {
Card card = game.getCard(objectId);
if (card != null) {
return (affectedControllerId.equals(source.getControllerId())
&& StaticFilters.FILTER_CARD_INSTANT_OR_SORCERY.match(card, game)
&& Zone.GRAVEYARD.equals(game.getState().getZone(card.getId())));
if (card != null
&& affectedControllerId.equals(source.getControllerId())
&& StaticFilters.FILTER_CARD_INSTANT_OR_SORCERY.match(card, game)
&& Zone.GRAVEYARD.equals(game.getState().getZone(card.getId()))) {
game.getState().setValue("JayaBallard", card);
return true;
}
return false;
}
@ -98,7 +100,7 @@ class JayaBallardReplacementEffect extends ReplacementEffectImpl {
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
Player controller = game.getPlayer(source.getControllerId());
if (controller != null) {
Card card = game.getCard(getTargetPointer().getFirst(game, source));
Card card = (Card) game.getState().getValue("JayaBallard");
if (card != null) {
controller.moveCardToExileWithInfo(card, null, "", source.getSourceId(), game, Zone.STACK, true);
return true;
@ -116,11 +118,13 @@ class JayaBallardReplacementEffect extends ReplacementEffectImpl {
public boolean applies(GameEvent event, Ability source, Game game) {
if (Zone.GRAVEYARD == ((ZoneChangeEvent) event).getToZone()) {
Card card = game.getCard(event.getSourceId());
if (card != null && (card.isInstant() || card.isSorcery())) {
// TODO: Find a way to check, that the spell from graveyard was really cast by the ability of the emblem.
// currently every spell cast from graveyard will be exiled.
if (card != null
&& (card.isInstant()
|| card.isSorcery())) {
CastFromGraveyardWatcher watcher = game.getState().getWatcher(CastFromGraveyardWatcher.class);
return watcher != null && watcher.spellWasCastFromGraveyard(event.getTargetId(), game.getState().getZoneChangeCounter(event.getTargetId()));
return watcher != null
&& watcher.spellWasCastFromGraveyard(event.getTargetId(),
game.getState().getZoneChangeCounter(event.getTargetId()));
}
}
return false;

View file

@ -0,0 +1,28 @@
package mage.game.command.emblems;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.common.continuous.GainAbilityAllEffect;
import mage.abilities.keyword.IndestructibleAbility;
import mage.constants.Duration;
import mage.constants.Zone;
import mage.filter.StaticFilters;
import mage.game.command.Emblem;
/**
* @author TheElk801
*/
public final class NissaWhoShakesTheWorldEmblem extends Emblem {
public NissaWhoShakesTheWorldEmblem() {
this.setName("Emblem Nissa");
this.getAbilities().add(new SimpleStaticAbility(
Zone.COMMAND,
new GainAbilityAllEffect(
IndestructibleAbility.getInstance(), Duration.WhileOnBattlefield,
StaticFilters.FILTER_CONTROLLED_PERMANENT_LAND, false
)
));
this.setExpansionSetCodeForImage("WAR");
}
}

View file

@ -6,6 +6,7 @@ import java.util.Objects;
import java.util.UUID;
import mage.cards.Card;
import mage.cards.ExpansionSet;
import org.apache.log4j.Logger;
/**
*
@ -13,7 +14,10 @@ import mage.cards.ExpansionSet;
*/
public class RichManBoosterDraft extends DraftImpl {
protected int[] richManTimes = {75, 70, 65, 60, 55, 50, 45, 40, 35, 35, 35, 35, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25};
private static final Logger logger = Logger.getLogger(RichManBoosterDraft.class);
//protected int[] richManTimes = {75, 70, 65, 60, 55, 50, 45, 40, 35, 35, 35, 35, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25};
protected int[] richManTimes = {70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40};
public RichManBoosterDraft(DraftOptions options, List<ExpansionSet> sets) {
super(options, sets);

View file

@ -13,7 +13,8 @@ import mage.game.draft.DraftCube.CardIdentity;
*/
public class RichManCubeBoosterDraft extends DraftImpl {
protected int[] richManTimes = {75, 70, 65, 60, 55, 50, 45, 40, 35, 35, 35, 35, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25};
//protected int[] richManTimes = {75, 70, 65, 60, 55, 50, 45, 40, 35, 35, 35, 35, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25};
protected int[] richManTimes = {70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40};
protected final Map<String, CardIdentity> cardsInCube = new LinkedHashMap<>();
public RichManCubeBoosterDraft(DraftOptions options, List<ExpansionSet> sets) {

View file

@ -18,6 +18,9 @@ public class GameEvent implements Serializable {
protected UUID sourceId;
protected UUID playerId;
protected int amount;
// flags:
// for counters: event is result of effect (+1 from planeswalkers is cost, not effect)
// for combat damage: event is preventable damage
protected boolean flag;
protected String data;
protected Zone zone;
@ -292,7 +295,7 @@ public class GameEvent implements Serializable {
UNATTACH, UNATTACHED,
ADD_COUNTER, COUNTER_ADDED,
ADD_COUNTERS, COUNTERS_ADDED,
COUNTER_REMOVED,
COUNTER_REMOVED, COUNTERS_REMOVED,
LOSE_CONTROL,
/* LOST_CONTROL
targetId id of the creature that lost control
@ -433,6 +436,17 @@ public class GameEvent implements Serializable {
this.amount = amount;
}
public void setAmountForCounters(int amount, boolean isEffect) {
this.amount = amount;
// cost event must be "transformed" to effect event, as example:
// planeswalker's +1 cost will be affected by Pir, Imaginative Rascal (1 + 1) and applied as effect by Doubling Season (2 * 2)
// https://github.com/magefree/mage/issues/5802
if (isEffect) {
setFlag(true);
}
}
public boolean getFlag() {
return flag;
}

View file

@ -909,7 +909,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
if (countersToRemove > getCounters(game).getCount(CounterType.LOYALTY)) {
countersToRemove = getCounters(game).getCount(CounterType.LOYALTY);
}
getCounters(game).removeCounter(CounterType.LOYALTY, countersToRemove);
removeCounters(CounterType.LOYALTY.getName(), countersToRemove, game);
game.fireEvent(new DamagedPlaneswalkerEvent(objectId, sourceId, controllerId, actualDamage, combat));
return actualDamage;
}

View file

@ -26,6 +26,8 @@ public final class AssassinToken2 extends TokenImpl {
toughness = new MageInt(1);
addAbility(DeathtouchAbility.getInstance());
addAbility(new AssassinToken2TriggeredAbility());
setOriginalExpansionSetCode("WAR");
}
private AssassinToken2(final AssassinToken2 token) {
@ -40,7 +42,7 @@ public final class AssassinToken2 extends TokenImpl {
class AssassinToken2TriggeredAbility extends TriggeredAbilityImpl {
AssassinToken2TriggeredAbility() {
super(Zone.BATTLEFIELD, new DestroyTargetEffect());
super(Zone.BATTLEFIELD, null);
}
private AssassinToken2TriggeredAbility(final AssassinToken2TriggeredAbility effect) {
@ -60,9 +62,10 @@ class AssassinToken2TriggeredAbility extends TriggeredAbilityImpl {
@Override
public boolean checkTrigger(GameEvent event, Game game) {
if (event.getSourceId().equals(getSourceId())) {
for (Effect effect : this.getAllEffects()) {
effect.setTargetPointer(new FixedTarget(event.getPlayerId()));
}
Effect effect = new DestroyTargetEffect();
effect.setTargetPointer(new FixedTarget(event.getTargetId(), game));
this.getEffects().clear();
this.addEffect(effect);
return true;
}
return false;

View file

@ -1,17 +1,15 @@
package mage.game.permanent.token;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import mage.MageInt;
import mage.abilities.keyword.FlyingAbility;
import mage.constants.CardType;
import mage.constants.SubType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
*
* @author BetaSteward_at_googlemail.com
*/
public final class DragonToken extends TokenImpl {
@ -19,7 +17,7 @@ public final class DragonToken extends TokenImpl {
static final private List<String> tokenImageSets = new ArrayList<>();
static {
tokenImageSets.addAll(Arrays.asList("DTK", "MMA", "ALA", "MM3", "C17"));
tokenImageSets.addAll(Arrays.asList("DTK", "MMA", "ALA", "MM3", "C17", "WAR"));
}
public DragonToken() {

View file

@ -19,7 +19,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", "ANA", "RNA"));
"MM3", "EMA", "C16", "DOM", "ANA", "RNA", "WAR"));
}
public GoblinToken(boolean withHaste) {

View file

@ -0,0 +1,33 @@
package mage.game.permanent.token;
import mage.MageInt;
import mage.abilities.keyword.VigilanceAbility;
import mage.constants.CardType;
import mage.constants.SubType;
/**
* @author TheElk801
*/
public final class GodEternalOketraToken extends TokenImpl {
public GodEternalOketraToken() {
super("Zombie Warrior", "4/4 black Zombie Warrior creature token with vigilance");
setOriginalExpansionSetCode("WAR"); // default
cardType.add(CardType.CREATURE);
color.setBlack(true);
subtype.add(SubType.ZOMBIE);
subtype.add(SubType.WARRIOR);
power = new MageInt(4);
toughness = new MageInt(4);
addAbility(VigilanceAbility.getInstance());
}
private GodEternalOketraToken(final GodEternalOketraToken token) {
super(token);
}
@Override
public GodEternalOketraToken copy() {
return new GodEternalOketraToken(this);
}
}

View file

@ -0,0 +1,34 @@
package mage.game.permanent.token;
import mage.MageInt;
import mage.constants.CardType;
import mage.constants.SubType;
/**
*
* @author TheElk801
*/
public final class PlanewideCelebrationToken extends TokenImpl {
public PlanewideCelebrationToken() {
super("Citizen", "2/2 Citizen creature token that's all colors");
cardType.add(CardType.CREATURE);
color.setWhite(true);
color.setBlue(true);
color.setBlack(true);
color.setRed(true);
color.setGreen(true);
subtype.add(SubType.CITIZEN);
power = new MageInt(2);
toughness = new MageInt(2);
}
public PlanewideCelebrationToken(final PlanewideCelebrationToken token) {
super(token);
}
public PlanewideCelebrationToken copy() {
return new PlanewideCelebrationToken(this);
}
}

View file

@ -1,35 +0,0 @@
package mage.game.permanent.token;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.MageInt;
import mage.ObjectColor;
import mage.abilities.keyword.HasteAbility;
/**
*
* @author spjspj
*/
public final class RevelOfTheFallenGodSatyrToken extends TokenImpl {
public RevelOfTheFallenGodSatyrToken() {
super("Satyr", "2/2 red and green Satyr creature tokens with haste");
this.setOriginalExpansionSetCode("THS");
cardType.add(CardType.CREATURE);
color.setColor(ObjectColor.RED);
color.setColor(ObjectColor.GREEN);
subtype.add(SubType.SATYR);
power = new MageInt(2);
toughness = new MageInt(2);
addAbility(HasteAbility.getInstance());
}
public RevelOfTheFallenGodSatyrToken(final RevelOfTheFallenGodSatyrToken token) {
super(token);
}
public RevelOfTheFallenGodSatyrToken copy() {
return new RevelOfTheFallenGodSatyrToken(this);
}
}

View file

@ -19,6 +19,7 @@ public final class ServoToken extends TokenImpl {
static {
tokenImageSets.addAll(Collections.singletonList("KLD"));
tokenImageSets.addAll(Collections.singletonList("WAR"));
}
public ServoToken() {

View file

@ -0,0 +1,34 @@
package mage.game.permanent.token;
import mage.MageInt;
import mage.abilities.keyword.VigilanceAbility;
import mage.constants.CardType;
import mage.constants.SubType;
/**
* @author TheElk801
*/
public final class SoldierVigilanceToken extends TokenImpl {
public SoldierVigilanceToken() {
super("Soldier", "2/2 white Soldier creature token with vigilance");
cardType.add(CardType.CREATURE);
color.setWhite(true);
subtype.add(SubType.SOLDIER);
power = new MageInt(2);
toughness = new MageInt(2);
addAbility(VigilanceAbility.getInstance());
setOriginalExpansionSetCode("WAR");
}
private SoldierVigilanceToken(final SoldierVigilanceToken token) {
super(token);
}
@Override
public SoldierVigilanceToken copy() {
return new SoldierVigilanceToken(this);
}
}

View file

@ -15,6 +15,8 @@ public final class TeyoToken extends TokenImpl {
power = new MageInt(0);
toughness = new MageInt(3);
addAbility(DefenderAbility.getInstance());
setOriginalExpansionSetCode("WAR");
}
public TeyoToken(final TeyoToken token) {

View file

@ -0,0 +1,29 @@
package mage.game.permanent.token;
import mage.MageInt;
import mage.constants.CardType;
import mage.constants.SubType;
/**
* @author TheElk801
*/
public final class UginTheIneffableToken extends TokenImpl {
public UginTheIneffableToken() {
super("Spirit", "2/2 colorless Spirit creature token");
setExpansionSetCodeForImage("WAR"); // default
cardType.add(CardType.CREATURE);
subtype.add(SubType.SPIRIT);
power = new MageInt(2);
toughness = new MageInt(2);
}
private UginTheIneffableToken(final UginTheIneffableToken token) {
super(token);
}
@Override
public UginTheIneffableToken copy() {
return new UginTheIneffableToken(this);
}
}

View file

@ -0,0 +1,35 @@
package mage.game.permanent.token;
import mage.MageInt;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.SuperType;
/**
* @author TheElk801
*/
public final class VojaFriendToElvesToken extends TokenImpl {
public VojaFriendToElvesToken() {
super("Voja, Friend to Elves", "Voja, Friend to Elves, a legendary 3/3 green and white Wolf creature token");
this.cardType.add(CardType.CREATURE);
addSuperType(SuperType.LEGENDARY);
this.subtype.add(SubType.WOLF);
this.color.setGreen(true);
this.color.setWhite(true);
this.power = new MageInt(3);
this.toughness = new MageInt(3);
setOriginalExpansionSetCode("WAR");
}
private VojaFriendToElvesToken(final VojaFriendToElvesToken token) {
super(token);
}
public VojaFriendToElvesToken copy() {
return new VojaFriendToElvesToken(this);
}
}

View file

@ -0,0 +1,31 @@
package mage.game.permanent.token;
import mage.MageInt;
import mage.constants.CardType;
import mage.constants.SubType;
public final class WizardToken extends TokenImpl {
public WizardToken() {
this("WAR");
}
public WizardToken(String setCode) {
super("Wizard", "2/2 blue Wizard creature token");
cardType.add(CardType.CREATURE);
subtype.add(SubType.WIZARD);
color.setBlue(true);
power = new MageInt(2);
toughness = new MageInt(2);
setOriginalExpansionSetCode(setCode);
}
private WizardToken(final WizardToken token) {
super(token);
}
public WizardToken copy() {
return new WizardToken(this);
}
}

View file

@ -18,7 +18,7 @@ public final class WolfToken extends TokenImpl {
static final private List<String> tokenImageSets = new ArrayList<>();
static {
tokenImageSets.addAll(Arrays.asList("BNG", "C14", "CNS", "FNMP", "ISD", "LRW", "M10", "M14", "MM2", "SHM", "SOM", "ZEN", "SOI", "C15", "M15"));
tokenImageSets.addAll(Arrays.asList("BNG", "C14", "CNS", "FNMP", "ISD", "LRW", "M10", "M14", "MM2", "SHM", "SOM", "ZEN", "SOI", "C15", "M15", "WAR"));
}
public WolfToken() {

View file

@ -18,7 +18,7 @@ public final class ZombieToken extends TokenImpl {
static {
tokenImageSets.addAll(Arrays.asList("10E", "M10", "M11", "M12", "M13", "M14", "M15", "MBS", "ALA", "ISD", "C14", "C15", "C16", "C17", "CNS",
"MMA", "BNG", "KTK", "DTK", "ORI", "OGW", "SOI", "EMN", "EMA", "MM3", "AKH", "CMA", "E01", "RNA"));
"MMA", "BNG", "KTK", "DTK", "ORI", "OGW", "SOI", "EMN", "EMA", "MM3", "AKH", "CMA", "E01", "RNA", "WAR"));
}
public ZombieToken() {

View file

@ -188,6 +188,11 @@ public abstract class StackObjImpl implements StackObject {
newTarget.clearChosen();
}
}
// workaround to stop infinite AI choose (remove after chooseTarget can be called with extra filter to disable some ids)
if (iteration > 10) {
break;
}
}
while (targetController.canRespond() && (targetId.equals(newTarget.getFirstTarget()) || newTarget.getTargets().size() != 1));
// choose a new target

View file

@ -1,15 +1,13 @@
package mage.game.tournament;
import mage.cards.decks.Deck;
import mage.game.draft.DraftCube;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import mage.cards.decks.Deck;
import mage.game.draft.DraftCube;
/**
*
* @author BetaSteward_at_googlemail.com
*/
public class LimitedOptions implements Serializable {
@ -20,6 +18,7 @@ public class LimitedOptions implements Serializable {
protected DraftCube draftCube;
protected int numberBoosters;
protected boolean isRandom;
protected boolean isRichMan;
protected Deck cubeFromDeck = null;
public List<String> getSetCodes() {
@ -66,11 +65,19 @@ public class LimitedOptions implements Serializable {
this.numberBoosters = numberBoosters;
}
public boolean getIsRandom(){
public boolean getIsRandom() {
return isRandom;
}
public void setIsRandom(boolean isRandom){
public void setIsRandom(boolean isRandom) {
this.isRandom = isRandom;
}
public boolean getIsRichMan() {
return isRichMan;
}
public void setIsRichMan(boolean isRichMan) {
this.isRichMan = isRichMan;
}
}

Some files were not shown because too many files have changed in this diff Show more