Cost increasing effects - refactor, removed redundant custom effects (related to #6684 and #6698);

This commit is contained in:
Oleg Agafonov 2020-06-30 08:27:29 +04:00
parent b885fffd9d
commit 09bc2575d8
23 changed files with 442 additions and 658 deletions

View file

@ -769,7 +769,7 @@ public abstract class AbilityImpl implements Ability {
if (ruleStart.length() > 1) {
String end = ruleStart.substring(ruleStart.length() - 2).trim();
if (end.isEmpty() || end.equals(":") || end.equals(".")) {
rule = ruleStart + Character.toUpperCase(text.charAt(0)) + text.substring(1);
rule = ruleStart + CardUtil.getTextWithFirstCharUpperCase(text);
} else {
rule = ruleStart + text;
}

View file

@ -4,6 +4,7 @@ import mage.abilities.Ability;
import mage.abilities.Mode;
import mage.constants.Outcome;
import mage.target.targetpointer.TargetPointer;
import mage.util.CardUtil;
import java.util.ArrayList;
@ -31,7 +32,7 @@ public class Effects extends ArrayList<Effect> {
public String getTextStartingUpperCase(Mode mode) {
String text = getText(mode);
if (text.length() > 3) {
return Character.toUpperCase(text.charAt(0)) + text.substring(1);
return CardUtil.getTextWithFirstCharUpperCase(text);
}
return text;
}

View file

@ -0,0 +1,166 @@
package mage.abilities.effects.common.cost;
import mage.abilities.Ability;
import mage.abilities.Mode;
import mage.abilities.SpellAbility;
import mage.constants.CostModificationType;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.constants.TargetController;
import mage.filter.FilterCard;
import mage.game.Game;
import mage.game.stack.Spell;
import mage.players.Player;
import mage.target.Target;
import mage.util.CardUtil;
import java.util.Collection;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
/**
* @author JayDi85
*/
public class SpellsCostModificationThatTargetSourceEffect extends CostModificationEffectImpl {
private final FilterCard spellFilter;
private final int modificationAmount;
private String targetName = "{this}";
private TargetController targetController;
public SpellsCostModificationThatTargetSourceEffect(int modificationAmount, FilterCard spellFilter, TargetController targetController) {
super(Duration.WhileOnBattlefield, Outcome.Neutral, modificationAmount < 0 ? CostModificationType.REDUCE_COST : CostModificationType.INCREASE_COST);
this.spellFilter = spellFilter;
this.modificationAmount = modificationAmount;
this.targetController = targetController;
setText();
}
private void setText() {
// example: Spells your opponents cast that target Accursed Witch cost {1} less to cast.
StringBuilder sb = new StringBuilder();
sb.append(this.spellFilter.getMessage());
switch (this.targetController) {
case ANY:
break;
case YOU:
sb.append(" you");
break;
case OPPONENT:
sb.append(" your opponents");
break;
default:
throw new IllegalArgumentException("Unsupported target controller " + this.targetController);
}
sb.append(" cast that target ").append(this.targetName);
if (this.modificationAmount < 0) {
sb.append(" cost {").append(-1 * this.modificationAmount).append("} less to cast");
} else {
sb.append(" cost {").append(this.modificationAmount).append("} more to cast");
}
this.staticText = sb.toString();
}
private SpellsCostModificationThatTargetSourceEffect(SpellsCostModificationThatTargetSourceEffect effect) {
super(effect);
this.spellFilter = effect.spellFilter;
this.modificationAmount = effect.modificationAmount;
this.targetName = effect.targetName;
this.targetController = effect.targetController;
}
@Override
public SpellsCostModificationThatTargetSourceEffect copy() {
return new SpellsCostModificationThatTargetSourceEffect(this);
}
@Override
public boolean apply(Game game, Ability source, Ability abilityToModify) {
if (this.modificationAmount >= 0) {
CardUtil.increaseCost(abilityToModify, this.modificationAmount);
} else {
CardUtil.reduceCost(abilityToModify, -1 * this.modificationAmount);
}
return true;
}
@Override
public boolean applies(Ability abilityToModify, Ability source, Game game) {
if (!(abilityToModify instanceof SpellAbility)) {
return false;
}
Player sourceController = game.getPlayer(source.getControllerId());
Player abilityController = game.getPlayer(abilityToModify.getControllerId());
if (sourceController == null || abilityController == null) {
return false;
}
switch (this.targetController) {
case ANY:
break;
case YOU:
if (!sourceController.getId().equals(abilityController.getId())) {
return false;
}
break;
case OPPONENT:
if (!sourceController.hasOpponent(abilityController.getId(), game)) {
return false;
}
break;
default:
return false;
}
Spell spell = (Spell) game.getStack().getStackObject(abilityToModify.getId());
if (spell != null && this.spellFilter.match(spell, game)) {
// real cast with put on stack
Set<UUID> allTargets = getAllSelectedTargets(abilityToModify, source, game);
return allTargets.contains(source.getSourceId());
} else {
// get playable and other staff without put on stack
// used at least for flashback ability because Flashback ability doesn't use stack
Set<UUID> allTargets = getAllPossibleTargets(abilityToModify, source, game);
switch (this.getModificationType()) {
case REDUCE_COST:
// reduce all the time
return allTargets.contains(source.getSourceId());
case INCREASE_COST:
// increase if can't target another (e.g. user can choose another target without cost increase)
return allTargets.contains(source.getSourceId()) && allTargets.size() <= 1;
}
}
return false;
}
private Set<UUID> getAllSelectedTargets(Ability abilityToModify, Ability source, Game game) {
return abilityToModify.getModes().getSelectedModes()
.stream()
.map(abilityToModify.getModes()::get)
.map(Mode::getTargets)
.flatMap(Collection::stream)
.map(Target::getTargets)
.flatMap(Collection::stream)
.collect(Collectors.toSet());
}
private Set<UUID> getAllPossibleTargets(Ability abilityToModify, Ability source, Game game) {
return abilityToModify.getModes().values()
.stream()
.map(Mode::getTargets)
.flatMap(Collection::stream)
.map(t -> t.possibleTargets(abilityToModify.getSourceId(), abilityToModify.getControllerId(), game))
.flatMap(Collection::stream)
.collect(Collectors.toSet());
}
public SpellsCostModificationThatTargetSourceEffect withTargetName(String targetName) {
this.targetName = targetName;
setText();
return this;
}
}

View file

@ -1,8 +1,5 @@
package mage.abilities.effects.common.cost;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.UUID;
import mage.Mana;
import mage.abilities.Ability;
import mage.abilities.SpellAbility;
@ -17,8 +14,11 @@ import mage.game.stack.Spell;
import mage.players.Player;
import mage.util.CardUtil;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.UUID;
/**
*
* @author LevelX2
*/
public class SpellsCostReductionAllEffect extends CostModificationEffectImpl {
@ -125,8 +125,10 @@ public class SpellsCostReductionAllEffect extends CostModificationEffectImpl {
if (abilityToModify instanceof SpellAbility) {
Spell spell = (Spell) game.getStack().getStackObject(abilityToModify.getId());
if (spell != null) {
// real cast with put on stack
return this.filter.match(spell, game) && selectedByRuntimeData(spell, source, game);
} else {
// get playable and other staff without put on stack
// used at least for flashback ability because Flashback ability doesn't use stack
Card sourceCard = game.getCard(abilityToModify.getSourceId());
return sourceCard != null && this.filter.match(sourceCard, game) && selectedByRuntimeData(sourceCard, source, game);

View file

@ -1,7 +1,5 @@
package mage.abilities.effects.common.cost;
import java.util.LinkedHashSet;
import java.util.Set;
import mage.MageObject;
import mage.Mana;
import mage.abilities.Ability;
@ -19,8 +17,10 @@ import mage.game.stack.Spell;
import mage.players.Player;
import mage.util.CardUtil;
import java.util.LinkedHashSet;
import java.util.Set;
/**
*
* @author North
*/
public class SpellsCostReductionControllerEffect extends CostModificationEffectImpl {
@ -116,9 +116,11 @@ public class SpellsCostReductionControllerEffect extends CostModificationEffectI
if (abilityToModify.isControlledBy(source.getControllerId())) {
Spell spell = (Spell) game.getStack().getStackObject(abilityToModify.getId());
if (spell != null) {
// real cast with put on stack
return this.filter.match(spell, source.getSourceId(), source.getControllerId(), game);
} else {
// used at least for flashback ability because Flashback ability doesn't use stack or for getPlayables where spell is not cast yet
// get playable and other staff without put on stack
// used at least for flashback ability because Flashback ability doesn't use stack
Card sourceCard = game.getCard(abilityToModify.getSourceId());
return sourceCard != null && this.filter.match(sourceCard, source.getSourceId(), source.getControllerId(), game);
}

View file

@ -3,6 +3,7 @@ package mage.abilities.hint;
import mage.abilities.Ability;
import mage.abilities.condition.Condition;
import mage.game.Game;
import mage.util.CardUtil;
import java.awt.*;
@ -18,15 +19,19 @@ public class ConditionHint implements Hint {
private Color falseColor;
private Boolean useIcons;
public ConditionHint(Condition condition) {
this(condition, condition.toString());
}
public ConditionHint(Condition condition, String textWithIcons) {
this(condition, textWithIcons, null, textWithIcons, null, true);
}
public ConditionHint(Condition condition, String trueText, Color trueColor, String falseText, Color falseColor, Boolean useIcons) {
this.condition = condition;
this.trueText = trueText;
this.trueText = CardUtil.getTextWithFirstCharUpperCase(trueText);
this.trueColor = trueColor;
this.falseText = falseText;
this.falseText = CardUtil.getTextWithFirstCharUpperCase(falseText);
this.falseColor = falseColor;
this.useIcons = useIcons;
}

View file

@ -800,4 +800,12 @@ public final class CardUtil {
return object.getAbilities();
}
}
public static String getTextWithFirstCharUpperCase(String text) {
if (text != null && text.length() >= 1) {
return Character.toUpperCase(text.charAt(0)) + text.substring(1);
} else {
return text;
}
}
}