mirror of
https://github.com/correl/mage.git
synced 2024-12-25 11:11:16 +00:00
* Cost reduction effects - fixed that some cards with cost reduction can't be played (example: Price of Fame, see #6685, #6684);
This commit is contained in:
parent
2252648f01
commit
520d75dba9
7 changed files with 70 additions and 32 deletions
|
@ -1,7 +1,5 @@
|
||||||
package mage.cards.m;
|
package mage.cards.m;
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.UUID;
|
|
||||||
import mage.abilities.Ability;
|
import mage.abilities.Ability;
|
||||||
import mage.abilities.common.SimpleStaticAbility;
|
import mage.abilities.common.SimpleStaticAbility;
|
||||||
import mage.abilities.condition.Condition;
|
import mage.abilities.condition.Condition;
|
||||||
|
@ -17,6 +15,9 @@ import mage.game.stack.StackObject;
|
||||||
import mage.target.Target;
|
import mage.target.Target;
|
||||||
import mage.target.TargetSpell;
|
import mage.target.TargetSpell;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author TheElk801
|
* @author TheElk801
|
||||||
*/
|
*/
|
||||||
|
@ -27,7 +28,7 @@ public final class MysticalDispute extends CardImpl {
|
||||||
|
|
||||||
// This spell costs {2} less to cast if it targets a blue spell.
|
// This spell costs {2} less to cast if it targets a blue spell.
|
||||||
this.addAbility(new SimpleStaticAbility(
|
this.addAbility(new SimpleStaticAbility(
|
||||||
Zone.ALL, new SpellCostReductionSourceEffect(2, MysticalDisputeCondition.instance)
|
Zone.ALL, new SpellCostReductionSourceEffect(2, MysticalDisputeCondition.instance).setCanWorksOnStackOnly(true)
|
||||||
).setRuleAtTheTop(true));
|
).setRuleAtTheTop(true));
|
||||||
|
|
||||||
// Counter target spell unless its controller pays {3}.
|
// Counter target spell unless its controller pays {3}.
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
package mage.cards.n;
|
package mage.cards.n;
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.UUID;
|
|
||||||
import mage.abilities.Ability;
|
import mage.abilities.Ability;
|
||||||
import mage.abilities.Mode;
|
import mage.abilities.Mode;
|
||||||
import mage.abilities.common.SimpleStaticAbility;
|
import mage.abilities.common.SimpleStaticAbility;
|
||||||
|
@ -21,6 +19,9 @@ import mage.game.stack.StackObject;
|
||||||
import mage.target.Target;
|
import mage.target.Target;
|
||||||
import mage.target.TargetStackObject;
|
import mage.target.TargetStackObject;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Rafbill
|
* @author Rafbill
|
||||||
*/
|
*/
|
||||||
|
@ -41,7 +42,7 @@ public final class NotOfThisWorld extends CardImpl {
|
||||||
this.getSpellAbility().addTarget(new TargetStackObject(filter));
|
this.getSpellAbility().addTarget(new TargetStackObject(filter));
|
||||||
|
|
||||||
// Not of This World costs {7} less to cast if it targets a spell or ability that targets a creature you control with power 7 or greater.
|
// Not of This World costs {7} less to cast if it targets a spell or ability that targets a creature you control with power 7 or greater.
|
||||||
this.addAbility(new SimpleStaticAbility(Zone.ALL, new SpellCostReductionSourceEffect(7, NotOfThisWorldCondition.instance)));
|
this.addAbility(new SimpleStaticAbility(Zone.ALL, new SpellCostReductionSourceEffect(7, NotOfThisWorldCondition.instance).setCanWorksOnStackOnly(true)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private NotOfThisWorld(final NotOfThisWorld card) {
|
private NotOfThisWorld(final NotOfThisWorld card) {
|
||||||
|
@ -86,8 +87,8 @@ enum NotOfThisWorldCondition implements Condition {
|
||||||
.flatMap(Collection::stream)
|
.flatMap(Collection::stream)
|
||||||
.map(game::getPermanentOrLKIBattlefield)
|
.map(game::getPermanentOrLKIBattlefield)
|
||||||
.anyMatch(permanent -> permanent != null && filter.match(
|
.anyMatch(permanent -> permanent != null && filter.match(
|
||||||
permanent, sourceSpell.getSourceId(), sourceSpell.getControllerId(), game
|
permanent, sourceSpell.getSourceId(), sourceSpell.getControllerId(), game
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package mage.cards.p;
|
package mage.cards.p;
|
||||||
|
|
||||||
import java.util.UUID;
|
|
||||||
import mage.abilities.common.SimpleStaticAbility;
|
import mage.abilities.common.SimpleStaticAbility;
|
||||||
import mage.abilities.condition.Condition;
|
import mage.abilities.condition.Condition;
|
||||||
import mage.abilities.condition.common.SourceTargetsPermanentCondition;
|
import mage.abilities.condition.common.SourceTargetsPermanentCondition;
|
||||||
|
@ -16,6 +15,8 @@ import mage.filter.FilterPermanent;
|
||||||
import mage.filter.common.FilterCreaturePermanent;
|
import mage.filter.common.FilterCreaturePermanent;
|
||||||
import mage.target.common.TargetCreaturePermanent;
|
import mage.target.common.TargetCreaturePermanent;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author TheElk801
|
* @author TheElk801
|
||||||
*/
|
*/
|
||||||
|
@ -35,7 +36,7 @@ public final class PriceOfFame extends CardImpl {
|
||||||
|
|
||||||
// This spell costs {2} less to cast if it targets a legendary creature.
|
// This spell costs {2} less to cast if it targets a legendary creature.
|
||||||
this.addAbility(new SimpleStaticAbility(
|
this.addAbility(new SimpleStaticAbility(
|
||||||
Zone.ALL, new SpellCostReductionSourceEffect(2, condition)
|
Zone.ALL, new SpellCostReductionSourceEffect(2, condition).setCanWorksOnStackOnly(true)
|
||||||
).setRuleAtTheTop(true));
|
).setRuleAtTheTop(true));
|
||||||
|
|
||||||
// Destroy target creature.
|
// Destroy target creature.
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package mage.cards.s;
|
package mage.cards.s;
|
||||||
|
|
||||||
import java.util.UUID;
|
|
||||||
import mage.abilities.common.SimpleStaticAbility;
|
import mage.abilities.common.SimpleStaticAbility;
|
||||||
import mage.abilities.condition.Condition;
|
import mage.abilities.condition.Condition;
|
||||||
import mage.abilities.condition.common.SourceTargetsPermanentCondition;
|
import mage.abilities.condition.common.SourceTargetsPermanentCondition;
|
||||||
|
@ -20,6 +19,8 @@ import mage.filter.common.FilterControlledCreaturePermanent;
|
||||||
import mage.target.common.TargetControlledCreaturePermanent;
|
import mage.target.common.TargetControlledCreaturePermanent;
|
||||||
import mage.target.common.TargetCreaturePermanent;
|
import mage.target.common.TargetCreaturePermanent;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author TheElk801
|
* @author TheElk801
|
||||||
*/
|
*/
|
||||||
|
@ -35,7 +36,7 @@ public final class SavageStomp extends CardImpl {
|
||||||
|
|
||||||
// Savage Stomp costs {2} less to cast if it targets a Dinosaur you control.
|
// Savage Stomp costs {2} less to cast if it targets a Dinosaur you control.
|
||||||
this.addAbility(new SimpleStaticAbility(
|
this.addAbility(new SimpleStaticAbility(
|
||||||
Zone.ALL, new SpellCostReductionSourceEffect(2, condition)
|
Zone.ALL, new SpellCostReductionSourceEffect(2, condition).setCanWorksOnStackOnly(true)
|
||||||
).setRuleAtTheTop(true));
|
).setRuleAtTheTop(true));
|
||||||
|
|
||||||
// Put a +1/+1 counter on target creature you control. Then that creature fights target creature you don't control.
|
// Put a +1/+1 counter on target creature you control. Then that creature fights target creature you don't control.
|
||||||
|
|
|
@ -2,14 +2,13 @@ package org.mage.test.cards.cost.modification;
|
||||||
|
|
||||||
import mage.constants.PhaseStep;
|
import mage.constants.PhaseStep;
|
||||||
import mage.constants.Zone;
|
import mage.constants.Zone;
|
||||||
import org.junit.Ignore;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.mage.test.serverside.base.CardTestPlayerBase;
|
import org.mage.test.serverside.base.CardTestPlayerBaseWithAIHelps;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author JayDi85
|
* @author JayDi85
|
||||||
*/
|
*/
|
||||||
public class CostReduceWithConditionTest extends CardTestPlayerBase {
|
public class CostReduceWithConditionTest extends CardTestPlayerBaseWithAIHelps {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test_PriceOfFame_Normal() {
|
public void test_PriceOfFame_Normal() {
|
||||||
|
@ -31,9 +30,9 @@ public class CostReduceWithConditionTest extends CardTestPlayerBase {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Ignore
|
public void test_PriceOfFame_Reduce_Manual() {
|
||||||
// TODO: implement workaround like putToStackAsNonPlayable for abilities, see https://github.com/magefree/mage/issues/6685
|
// https://github.com/magefree/mage/issues/6685
|
||||||
public void test_PriceOfFame_Reduce() {
|
|
||||||
// {3}{B}
|
// {3}{B}
|
||||||
// This spell costs {2} less to cast if it targets a legendary creature.
|
// This spell costs {2} less to cast if it targets a legendary creature.
|
||||||
// Destroy target creature.
|
// Destroy target creature.
|
||||||
|
@ -50,4 +49,26 @@ public class CostReduceWithConditionTest extends CardTestPlayerBase {
|
||||||
|
|
||||||
assertGraveyardCount(playerB, "Anje Falkenrath", 1);
|
assertGraveyardCount(playerB, "Anje Falkenrath", 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_PriceOfFame_Reduce_AI() {
|
||||||
|
// https://github.com/magefree/mage/issues/6685
|
||||||
|
|
||||||
|
// {3}{B}
|
||||||
|
// This spell costs {2} less to cast if it targets a legendary creature.
|
||||||
|
// Destroy target creature.
|
||||||
|
addCard(Zone.HAND, playerA, "Price of Fame", 1);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 4 - 2);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerB, "Anje Falkenrath", 1);
|
||||||
|
|
||||||
|
// AI must see and play that cards too
|
||||||
|
aiPlayPriority(1, PhaseStep.PRECOMBAT_MAIN, playerA);
|
||||||
|
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
setStopAt(1, PhaseStep.BEGIN_COMBAT);
|
||||||
|
execute();
|
||||||
|
assertAllCommandsUsed();
|
||||||
|
|
||||||
|
assertGraveyardCount(playerB, "Anje Falkenrath", 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,27 +1,28 @@
|
||||||
|
|
||||||
|
|
||||||
package mage.abilities.effects.common.cost;
|
package mage.abilities.effects.common.cost;
|
||||||
|
|
||||||
import mage.constants.Duration;
|
|
||||||
import mage.constants.EffectType;
|
|
||||||
import mage.constants.Outcome;
|
|
||||||
import mage.abilities.Ability;
|
import mage.abilities.Ability;
|
||||||
import mage.abilities.effects.ContinuousEffectImpl;
|
import mage.abilities.effects.ContinuousEffectImpl;
|
||||||
import mage.abilities.effects.CostModificationEffect;
|
import mage.abilities.effects.CostModificationEffect;
|
||||||
import mage.constants.CostModificationType;
|
import mage.constants.CostModificationType;
|
||||||
|
import mage.constants.Duration;
|
||||||
|
import mage.constants.EffectType;
|
||||||
|
import mage.constants.Outcome;
|
||||||
import mage.game.Game;
|
import mage.game.Game;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Simple implementation of a {@link CostModificationEffect} offering simplified
|
* Simple implementation of a {@link CostModificationEffect} offering simplified
|
||||||
* construction to setup the object for use by the mage framework.
|
* construction to setup the object for use by the mage framework.
|
||||||
|
*
|
||||||
* @author maurer.it_at_gmail.com
|
* @author maurer.it_at_gmail.com
|
||||||
*/
|
*/
|
||||||
public abstract class CostModificationEffectImpl extends ContinuousEffectImpl implements CostModificationEffect {
|
public abstract class CostModificationEffectImpl extends ContinuousEffectImpl implements CostModificationEffect {
|
||||||
|
|
||||||
private final CostModificationType modificationType;
|
private final CostModificationType modificationType;
|
||||||
|
|
||||||
public CostModificationEffectImpl ( Duration duration, Outcome outcome, CostModificationType type) {
|
// if effect need real stack object to check then mark it as stack only (example: apply cost reduction if you target human creature)
|
||||||
|
private boolean worksOnStackOnly = false;
|
||||||
|
|
||||||
|
public CostModificationEffectImpl(Duration duration, Outcome outcome, CostModificationType type) {
|
||||||
super(duration, outcome);
|
super(duration, outcome);
|
||||||
this.effectType = EffectType.COSTMODIFICATION;
|
this.effectType = EffectType.COSTMODIFICATION;
|
||||||
this.modificationType = type;
|
this.modificationType = type;
|
||||||
|
@ -30,23 +31,33 @@ public abstract class CostModificationEffectImpl extends ContinuousEffectImpl im
|
||||||
public CostModificationEffectImpl(final CostModificationEffectImpl effect) {
|
public CostModificationEffectImpl(final CostModificationEffectImpl effect) {
|
||||||
super(effect);
|
super(effect);
|
||||||
this.modificationType = effect.modificationType;
|
this.modificationType = effect.modificationType;
|
||||||
|
this.worksOnStackOnly = effect.worksOnStackOnly;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Overridden and 'no-op' implementation put in place.
|
* Overridden and 'no-op' implementation put in place.
|
||||||
*
|
*
|
||||||
* @see #apply(mage.game.Game, mage.abilities.Ability, mage.abilities.Ability)
|
|
||||||
*
|
|
||||||
* @param game
|
* @param game
|
||||||
* @param source
|
* @param source
|
||||||
* @return
|
* @return
|
||||||
|
* @see #apply(mage.game.Game, mage.abilities.Ability, mage.abilities.Ability)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public final boolean apply ( Game game, Ability source ) { return false; }
|
public final boolean apply(Game game, Ability source) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CostModificationType getModificationType(){
|
public CostModificationType getModificationType() {
|
||||||
return this.modificationType;
|
return this.modificationType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CostModificationEffectImpl setCanWorksOnStackOnly(boolean worksOnStackOnly) {
|
||||||
|
this.worksOnStackOnly = worksOnStackOnly;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean canWorksOnStackOnly() {
|
||||||
|
return this.worksOnStackOnly;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,7 +93,9 @@ public class SpellCostReductionSourceEffect extends CostModificationEffectImpl {
|
||||||
@Override
|
@Override
|
||||||
public boolean applies(Ability abilityToModify, Ability source, Game game) {
|
public boolean applies(Ability abilityToModify, Ability source, Game game) {
|
||||||
if (abilityToModify.getSourceId().equals(source.getSourceId()) && (abilityToModify instanceof SpellAbility)) {
|
if (abilityToModify.getSourceId().equals(source.getSourceId()) && (abilityToModify instanceof SpellAbility)) {
|
||||||
return condition == null || condition.apply(game, source);
|
// some conditions can works after put on stack, so skip it in get playable (allows user to put card on stack anyway)
|
||||||
|
boolean skipCondition = game.inCheckPlayableState() && canWorksOnStackOnly();
|
||||||
|
return condition == null || skipCondition || condition.apply(game, source);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue