Rework affinity implementation (#13707)

* rework affinity implementation

* move filters
This commit is contained in:
Evan Kranzler 2025-05-31 22:00:13 -04:00 committed by Failure
parent 2d237fdba1
commit 0b714946b0
41 changed files with 310 additions and 466 deletions

View file

@ -9,11 +9,11 @@ import mage.game.Game;
/**
* @author JayDi85
*/
public enum GateYouControlHint implements Hint {
public enum GatesYouControlHint implements Hint {
instance;
private static final Hint hint = new ValueHint("Gate you control", GateYouControlCount.instance);
private static final Hint hint = new ValueHint("Gates you control", GateYouControlCount.instance);
@Override
public String getText(Game game, Ability ability) {

View file

@ -1,11 +1,10 @@
package mage.abilities.effects.common;
package mage.abilities.keyword;
import mage.abilities.Ability;
import mage.abilities.SpellAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.common.cost.CostModificationEffectImpl;
import mage.constants.CostModificationType;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.constants.*;
import mage.filter.common.FilterControlledPermanent;
import mage.game.Game;
import mage.util.CardUtil;
@ -15,15 +14,45 @@ import mage.util.CardUtil;
* 702.40a Affinity is a static ability that functions while the spell with affinity is on the stack.
* Affinity for [text] means This spell costs you {1} less to cast for each [text] you control.
* 702.40b If a spell has multiple instances of affinity, each of them applies.
*
* @author Loki, TheElk801
*/
public class AffinityEffect extends CostModificationEffectImpl {
public class AffinityAbility extends SimpleStaticAbility {
private AffinityType affinityType;
public AffinityAbility(AffinityType affinityType) {
super(Zone.ALL, new AffinityEffect(affinityType.getFilter()));
setRuleAtTheTop(true);
this.addHint(affinityType.getHint());
this.affinityType = affinityType;
}
protected AffinityAbility(final AffinityAbility ability) {
super(ability);
this.affinityType = ability.affinityType;
}
@Override
public AffinityAbility copy() {
return new AffinityAbility(this);
}
@Override
public String getRule() {
return "Affinity for " + affinityType.getFilter().getMessage() +
" <i>(This spell costs {1} less to cast for each " +
affinityType.getSingularName() + " you control.)</i>";
}
}
class AffinityEffect extends CostModificationEffectImpl {
private final FilterControlledPermanent filter;
public AffinityEffect(FilterControlledPermanent affinityFilter) {
super(Duration.WhileOnStack, Outcome.Benefit, CostModificationType.REDUCE_COST);
this.filter = affinityFilter;
staticText = "Affinity for " + filter.getMessage();
}
protected AffinityEffect(final AffinityEffect effect) {
@ -34,14 +63,18 @@ public class AffinityEffect extends CostModificationEffectImpl {
@Override
public boolean apply(Game game, Ability source, Ability abilityToModify) {
// abilityToModify.getControllerId() works with Sen Triplets and in multiplayer games, see https://github.com/magefree/mage/issues/5931
int count = game.getBattlefield().getActivePermanents(filter, abilityToModify.getControllerId(), source, game).size();
CardUtil.reduceCost(abilityToModify, count);
CardUtil.reduceCost(
abilityToModify, game.getBattlefield().count(
filter, abilityToModify.getControllerId(), source, game
)
);
return true;
}
@Override
public boolean applies(Ability abilityToModify, Ability source, Game game) {
return abilityToModify instanceof SpellAbility && abilityToModify.getSourceId().equals(source.getSourceId());
return abilityToModify instanceof SpellAbility
&& abilityToModify.getSourceId().equals(source.getSourceId());
}
@Override

View file

@ -1,21 +1,14 @@
package mage.abilities.keyword;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.common.AffinityEffect;
import mage.abilities.hint.common.ArtifactYouControlHint;
import mage.constants.Zone;
import mage.filter.StaticFilters;
import mage.constants.AffinityType;
/**
* Affinity for artifacts
*/
public class AffinityForArtifactsAbility extends SimpleStaticAbility {
public class AffinityForArtifactsAbility extends AffinityAbility {
public AffinityForArtifactsAbility() {
super(Zone.ALL, new AffinityEffect(StaticFilters.FILTER_CONTROLLED_PERMANENT_ARTIFACT));
setRuleAtTheTop(true);
this.addHint(ArtifactYouControlHint.instance);
super(AffinityType.ARTIFACTS);
}
protected AffinityForArtifactsAbility(final AffinityForArtifactsAbility ability) {
@ -26,9 +19,4 @@ public class AffinityForArtifactsAbility extends SimpleStaticAbility {
public AffinityForArtifactsAbility copy() {
return new AffinityForArtifactsAbility(this);
}
@Override
public String getRule() {
return "Affinity for artifacts <i>(This spell costs {1} less to cast for each artifact you control.)</i>";
}
}

View file

@ -1,42 +0,0 @@
package mage.abilities.keyword;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount;
import mage.abilities.effects.common.AffinityEffect;
import mage.abilities.hint.ValueHint;
import mage.constants.SubType;
import mage.constants.Zone;
import mage.filter.common.FilterControlledPermanent;
/**
* @author LevelX2
*/
public class AffinityForLandTypeAbility extends SimpleStaticAbility {
private final String rulesText;
public AffinityForLandTypeAbility(SubType landType, String pluralName) {
super(Zone.ALL, null);
rulesText = "Affinity for " + pluralName + " <i>(This spell costs {1} less to cast for each " + landType + " you control.)</i>";
setRuleAtTheTop(true);
FilterControlledPermanent filter = new FilterControlledPermanent(landType);
addEffect(new AffinityEffect(filter));
addHint(new ValueHint(pluralName + " you control", new PermanentsOnBattlefieldCount(filter)));
}
protected AffinityForLandTypeAbility(final AffinityForLandTypeAbility ability) {
super(ability);
this.rulesText = ability.rulesText;
}
@Override
public AffinityForLandTypeAbility copy() {
return new AffinityForLandTypeAbility(this);
}
@Override
public String getRule() {
return rulesText;
}
}

View file

@ -0,0 +1,119 @@
package mage.constants;
import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount;
import mage.abilities.hint.Hint;
import mage.abilities.hint.ValueHint;
import mage.abilities.hint.common.ArtifactYouControlHint;
import mage.abilities.hint.common.CreaturesYouControlHint;
import mage.abilities.hint.common.GatesYouControlHint;
import mage.filter.common.*;
import mage.filter.predicate.mageobject.HistoricPredicate;
import mage.filter.predicate.mageobject.OutlawPredicate;
import mage.filter.predicate.permanent.TokenPredicate;
import mage.util.CardUtil;
/**
* @author TheElk801
*/
public enum AffinityType {
ARTIFACTS(new FilterControlledArtifactPermanent("artifacts"), ArtifactYouControlHint.instance),
CREATURES(new FilterControlledCreaturePermanent("creatures"), CreaturesYouControlHint.instance),
ARTIFACT_CREATURES(AffinityFilters.ARTIFACT_CREATURES),
ENCHANTMENTS(new FilterControlledEnchantmentPermanent("enchantments")),
PLANESWALKERS(new FilterControlledPlaneswalkerPermanent("planeswalker")),
EQUIPMENT(new FilterControlledPermanent(SubType.EQUIPMENT, "Equipment"), "Equipment"),
AURAS(new FilterControlledPermanent(SubType.AURA, "Auras")),
FOOD(new FilterControlledPermanent(SubType.FOOD, "Food"), "Food"),
TOKENS(AffinityFilters.TOKENS),
PLAINS(new FilterControlledPermanent(SubType.PLAINS, "Plains")),
ISLANDS(new FilterControlledPermanent(SubType.ISLAND, "Islands")),
SWAMPS(new FilterControlledPermanent(SubType.SWAMP, "Swamps")),
MOUNTAINS(new FilterControlledPermanent(SubType.MOUNTAIN, "Mountains")),
FORESTS(new FilterControlledPermanent(SubType.FOREST, "Forests")),
SPIRITS(new FilterControlledPermanent(SubType.SPIRIT, "Spirits")),
HUMANS(new FilterControlledPermanent(SubType.HUMAN, "Humans")),
KNIGHTS(new FilterControlledPermanent(SubType.KNIGHT, "Knights")),
DALEKS(new FilterControlledPermanent(SubType.DALEK, "Daleks")),
FROGS(new FilterControlledPermanent(SubType.FROG, "Frogs")),
LIZARDS(new FilterControlledPermanent(SubType.LIZARD, "Lizards")),
BIRDS(new FilterControlledPermanent(SubType.BIRD, "Birds")),
CITIZENS(new FilterControlledPermanent(SubType.CITIZEN, "Citizens")),
TOWNS(new FilterControlledPermanent(SubType.TOWN, "Towns")),
GATES(new FilterControlledPermanent(SubType.GATE, "Gates"), GatesYouControlHint.instance),
SNOW_LANDS(AffinityFilters.SNOW_LANDS),
HISTORIC(AffinityFilters.HISTORIC),
OUTLAWS(AffinityFilters.OUTLAWS);
private final FilterControlledPermanent filter;
private final Hint hint;
private final String singularName;
AffinityType(FilterControlledPermanent filter) {
this(filter, filter.getMessage());
}
AffinityType(FilterControlledPermanent filter, String singularName) {
this(filter, new ValueHint(
CardUtil.getTextWithFirstCharUpperCase(filter.getMessage()) + " you control",
new PermanentsOnBattlefieldCount(filter)
), singularName);
}
AffinityType(FilterControlledPermanent filter, Hint hint) {
this(filter, hint, filter.getMessage().substring(0, filter.getMessage().length() - 1));
}
AffinityType(FilterControlledPermanent filter, Hint hint, String singularName) {
this.filter = filter;
this.hint = hint;
this.singularName = singularName;
}
public FilterControlledPermanent getFilter() {
return filter;
}
public Hint getHint() {
return hint;
}
public String getSingularName() {
return singularName;
}
}
class AffinityFilters {
public static final FilterControlledPermanent TOKENS = new FilterControlledPermanent("tokens");
static {
TOKENS.add(TokenPredicate.TRUE);
}
public static final FilterControlledPermanent SNOW_LANDS = new FilterControlledLandPermanent("snow lands");
static {
SNOW_LANDS.add(SuperType.SNOW.getPredicate());
}
public static final FilterControlledPermanent OUTLAWS = new FilterControlledPermanent("outlaws");
static {
OUTLAWS.add(OutlawPredicate.instance);
}
public static final FilterControlledPermanent HISTORIC = new FilterControlledPermanent("historic permanents");
static {
HISTORIC.add(HistoricPredicate.instance);
}
public static final FilterControlledPermanent ARTIFACT_CREATURES = new FilterControlledPermanent("artifact creatures");
static {
ARTIFACT_CREATURES.add(CardType.ARTIFACT.getPredicate());
ARTIFACT_CREATURES.add(CardType.CREATURE.getPredicate());
}
}

View file

@ -1330,5 +1330,4 @@ public final class StaticFilters {
static {
FILTER_CONTROLLED_CLUE.setLockedFilter(true);
}
}