Cost reduction effects - refactor, removed redundant custom effects, added card hints;

This commit is contained in:
Oleg Agafonov 2020-06-29 12:52:14 +04:00
parent e4ebf50d42
commit cf3feff76a
35 changed files with 506 additions and 643 deletions

View file

@ -1,38 +1,60 @@
package mage.abilities.dynamicvalue.common;
import mage.abilities.Ability;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.effects.Effect;
import mage.filter.common.FilterCreaturePermanent;
import mage.game.Game;
import mage.game.combat.CombatGroup;
import mage.game.permanent.Permanent;
import java.util.UUID;
/**
*
* @author LevelX2
*/
public class AttackingCreatureCount implements DynamicValue {
private String message;
private FilterCreaturePermanent filter;
public AttackingCreatureCount() {
this("attacking creature");
}
public AttackingCreatureCount(FilterCreaturePermanent filter) {
this(filter, "attacking " + filter.getMessage());
}
public AttackingCreatureCount(String message) {
this(null, message);
}
public AttackingCreatureCount(FilterCreaturePermanent filter, String message) {
this.message = message;
this.filter = filter;
}
public AttackingCreatureCount(final AttackingCreatureCount dynamicValue) {
super();
this.message = dynamicValue.message;
this.filter = dynamicValue.filter;
}
@Override
public int calculate(Game game, Ability sourceAbility, Effect effect) {
int count = 0;
for (CombatGroup combatGroup : game.getCombat().getGroups()) {
count += combatGroup.getAttackers().size();
for (UUID permId : combatGroup.getAttackers()) {
if (filter != null) {
Permanent attacker = game.getPermanent(permId);
if (attacker != null && filter.match(attacker, sourceAbility.getSourceId(), sourceAbility.getControllerId(), game)) {
count++;
}
} else {
count++;
}
}
}
return count;
}

View file

@ -1,65 +0,0 @@
package mage.abilities.dynamicvalue.common;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.effects.Effect;
import mage.filter.common.FilterCreaturePermanent;
import mage.game.Game;
import mage.game.combat.CombatGroup;
import mage.game.permanent.Permanent;
/**
*
* @author escplan9 (Derek Monturo - dmontur1 at gmail dot com)
*/
public class AttackingFilterCreatureCount implements DynamicValue {
private FilterCreaturePermanent filter;
private String message;
public AttackingFilterCreatureCount(FilterCreaturePermanent filter) {
this(filter, "attacking creature");
}
public AttackingFilterCreatureCount(FilterCreaturePermanent filter, String message) {
this.filter = filter;
this.message = message;
}
public AttackingFilterCreatureCount(final AttackingFilterCreatureCount dynamicValue) {
super();
this.message = dynamicValue.message;
this.filter = dynamicValue.filter;
}
@Override
public int calculate(Game game, Ability sourceAbility, Effect effect) {
int count = 0;
for (CombatGroup combatGroup : game.getCombat().getGroups()) {
for (UUID permId : combatGroup.getAttackers()) {
Permanent attacker = game.getPermanent(permId);
if (filter.match(attacker, sourceAbility.getSourceId(), sourceAbility.getControllerId(), game)) {
count++;
}
}
}
return count;
}
@Override
public AttackingFilterCreatureCount copy() {
return new AttackingFilterCreatureCount(this);
}
@Override
public String getMessage() {
return message;
}
@Override
public String toString() {
return "X";
}
}

View file

@ -32,11 +32,11 @@ public enum GateYouControlCount implements DynamicValue {
@Override
public String toString() {
return "X";
return "1"; // uses "for each" effects, so must be 1, not X
}
@Override
public String getMessage() {
return "gate you control";
return "Gate you control";
}
}

View file

@ -1,7 +1,5 @@
package mage.abilities.effects.common.continuous;
import java.util.Iterator;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.dynamicvalue.DynamicValue;
@ -16,8 +14,9 @@ import mage.filter.common.FilterCreaturePermanent;
import mage.game.Game;
import mage.game.permanent.Permanent;
import java.util.Iterator;
/**
*
* @author BetaSteward_at_googlemail.com
*/
public class BoostControlledEffect extends ContinuousEffectImpl {
@ -56,10 +55,10 @@ public class BoostControlledEffect extends ContinuousEffectImpl {
* @param power
* @param toughness
* @param duration
* @param filter AnotherPredicate is not working, you need to use the
* excludeSource option
* @param lockedIn if true, power and toughness will be calculated only
* once, when the ability resolves
* @param filter AnotherPredicate is not working, you need to use the
* excludeSource option
* @param lockedIn if true, power and toughness will be calculated only
* once, when the ability resolves
* @param excludeSource
*/
public BoostControlledEffect(DynamicValue power, DynamicValue toughness, Duration duration, FilterCreaturePermanent filter, boolean excludeSource, boolean lockedIn) {
@ -105,7 +104,7 @@ public class BoostControlledEffect extends ContinuousEffectImpl {
@Override
public boolean apply(Game game, Ability source) {
if (this.affectedObjectsSet) {
for (Iterator<MageObjectReference> it = affectedObjectList.iterator(); it.hasNext();) {
for (Iterator<MageObjectReference> it = affectedObjectList.iterator(); it.hasNext(); ) {
Permanent permanent = it.next().getPermanent(game);
if (permanent != null) {
permanent.addPower(power.calculate(game, source, this));
@ -126,7 +125,6 @@ public class BoostControlledEffect extends ContinuousEffectImpl {
}
private void setText() {
String message = null;
StringBuilder sb = new StringBuilder();
if (excludeSource) {
sb.append("other ");
@ -150,6 +148,9 @@ public class BoostControlledEffect extends ContinuousEffectImpl {
sb.append(t);
sb.append((duration == Duration.EndOfTurn ? " until end of turn" : ""));
// where X
String message = null;
if (t.equals("X")) {
message = toughness.getMessage();
} else if (p.equals("X")) {
@ -158,6 +159,17 @@ public class BoostControlledEffect extends ContinuousEffectImpl {
if (message != null && !message.isEmpty()) {
sb.append(", where X is ").append(message);
}
// for each
if (message == null) {
message = toughness.getMessage();
if (message.isEmpty()) {
message = power.getMessage();
}
if (!message.isEmpty()) {
sb.append(" for each " + message);
}
}
staticText = sb.toString();
}

View file

@ -12,6 +12,12 @@ import mage.game.Game;
/**
* Simple implementation of a {@link CostModificationEffect} offering simplified
* construction to setup the object for use by the mage framework.
* <p>
* WARNING, if you implement custom effect and it can works on stack only (e.g. it need spell's targets to check) then
* use different apply code:
* - one for get playable mode before spell puts on stack (apply maximum possible cost reduction, use game.inCheckPlayableState()).
* - one for normal mode after spell puts on stack (apply real cost reduction)
* Example: TorgaarFamineIncarnate
*
* @author maurer.it_at_gmail.com
*/

View file

@ -1,59 +0,0 @@
package mage.abilities.effects.common.cost;
import mage.abilities.Ability;
import mage.abilities.SpellAbility;
import mage.constants.CostModificationType;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.filter.FilterCard;
import mage.filter.StaticFilters;
import mage.game.Game;
import mage.players.Player;
import mage.util.CardUtil;
/**
* @author Styxo
*/
public class SourceCostReductionForEachCardInGraveyardEffect extends CostModificationEffectImpl {
private FilterCard filter;
public SourceCostReductionForEachCardInGraveyardEffect() {
this(StaticFilters.FILTER_CARD);
}
public SourceCostReductionForEachCardInGraveyardEffect(FilterCard filter) {
super(Duration.WhileOnStack, Outcome.Benefit, CostModificationType.REDUCE_COST);
this.filter = filter;
staticText = "{this} costs {1} less to cast for each " + filter.getMessage() + " in your graveyard";
}
private SourceCostReductionForEachCardInGraveyardEffect(SourceCostReductionForEachCardInGraveyardEffect effect) {
super(effect);
this.filter = effect.filter.copy();
}
@Override
public boolean apply(Game game, Ability source, Ability abilityToModify) {
Player player = game.getPlayer(source.getControllerId());
if (player != null) {
int reductionAmount = player.getGraveyard().count(filter, game);
CardUtil.reduceCost(abilityToModify, reductionAmount);
return true;
}
return false;
}
@Override
public boolean applies(Ability abilityToModify, Ability source, Game game) {
if ((abilityToModify instanceof SpellAbility) && abilityToModify.getSourceId().equals(source.getSourceId())) {
return game.getCard(abilityToModify.getSourceId()) != null;
}
return false;
}
@Override
public SourceCostReductionForEachCardInGraveyardEffect copy() {
return new SourceCostReductionForEachCardInGraveyardEffect(this);
}
}

View file

@ -0,0 +1,93 @@
package mage.abilities.effects.common.cost;
import mage.abilities.Ability;
import mage.abilities.SpellAbility;
import mage.abilities.costs.mana.ManaCost;
import mage.abilities.costs.mana.ManaCosts;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.constants.CostModificationType;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.game.Game;
import mage.util.CardUtil;
/**
* @author JayDi85
*/
public class SpellCostReductionForEachSourceEffect extends CostModificationEffectImpl {
private final DynamicValue eachAmount;
private ManaCosts<ManaCost> reduceManaCosts;
private final int reduceGenericMana;
public SpellCostReductionForEachSourceEffect(int reduceGenericMana, DynamicValue eachAmount) {
super(Duration.WhileOnBattlefield, Outcome.Benefit, CostModificationType.REDUCE_COST);
this.eachAmount = eachAmount;
this.reduceManaCosts = null;
this.reduceGenericMana = reduceGenericMana;
StringBuilder sb = new StringBuilder();
sb.append("this spell costs {")
.append(this.reduceGenericMana)
.append("} less to cast for each ")
.append(this.eachAmount.getMessage());
this.staticText = sb.toString();
}
public SpellCostReductionForEachSourceEffect(ManaCosts<ManaCost> reduceManaCosts, DynamicValue eachAmount) {
super(Duration.WhileOnBattlefield, Outcome.Benefit, CostModificationType.REDUCE_COST);
this.eachAmount = eachAmount;
this.reduceManaCosts = reduceManaCosts;
this.reduceGenericMana = 0;
StringBuilder sb = new StringBuilder();
sb.append("this spell costs ");
for (String manaSymbol : reduceManaCosts.getSymbols()) {
sb.append(manaSymbol);
}
sb.append(" less to cast for each ").append(this.eachAmount.getMessage());
this.staticText = sb.toString();
}
protected SpellCostReductionForEachSourceEffect(final SpellCostReductionForEachSourceEffect effect) {
super(effect);
this.eachAmount = effect.eachAmount;
this.reduceManaCosts = effect.reduceManaCosts;
this.reduceGenericMana = effect.reduceGenericMana;
}
@Override
public boolean apply(Game game, Ability source, Ability abilityToModify) {
int needReduceAmount = eachAmount.calculate(game, source, this);
if (needReduceAmount > 0) {
if (reduceManaCosts != null) {
// color reduce
ManaCosts<ManaCost> needReduceMana = new ManaCostsImpl<>();
for (int i = 0; i <= needReduceAmount; i++) {
needReduceMana.add(reduceManaCosts.copy());
}
CardUtil.adjustCost((SpellAbility) abilityToModify, needReduceMana, false);
} else {
// generic reduce
CardUtil.reduceCost(abilityToModify, needReduceAmount * this.reduceGenericMana);
}
}
return true;
}
@Override
public boolean applies(Ability abilityToModify, Ability source, Game game) {
if (abilityToModify.getSourceId().equals(source.getSourceId()) && (abilityToModify instanceof SpellAbility)) {
return true;
}
return false;
}
@Override
public SpellCostReductionForEachSourceEffect copy() {
return new SpellCostReductionForEachSourceEffect(this);
}
}

View file

@ -37,11 +37,10 @@ public class SpellCostReductionSourceEffect extends CostModificationEffectImpl {
for (String manaSymbol : manaCostsToReduce.getSymbols()) {
sb.append(manaSymbol);
}
sb.append(" less");
sb.append(" less to cast");
if (this.condition != null) {
sb.append(" to if ").append(this.condition.toString());
sb.append(" if ").append(this.condition.toString());
}
this.staticText = sb.toString();
}

View file

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

View file

@ -1,12 +1,8 @@
/*
* 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.keyword;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.common.cost.SpellCostReductionSourceForOpponentsEffect;
import mage.abilities.dynamicvalue.common.OpponentsCount;
import mage.abilities.effects.common.cost.SpellCostReductionForEachSourceEffect;
import mage.constants.Zone;
/**
@ -15,7 +11,7 @@ import mage.constants.Zone;
public class UndauntedAbility extends SimpleStaticAbility {
public UndauntedAbility() {
super(Zone.ALL, new SpellCostReductionSourceForOpponentsEffect("undaunted <i>(This spell costs {1} less to cast for each opponent.)</i>"));
super(Zone.ALL, new SpellCostReductionForEachSourceEffect(1, OpponentsCount.instance));
setRuleAtTheTop(true);
}
@ -28,4 +24,8 @@ public class UndauntedAbility extends SimpleStaticAbility {
return new UndauntedAbility(this);
}
@Override
public String getRule() {
return "undaunted <i>(This spell costs {1} less to cast for each opponent.)</i>";
}
}