[WHO] Time Reaper, Add target adjuster for "that player controls/owns" damage trigger targets (#12528)

* Implement Time Reaper, start rework

* Create DamagedPlayerControlsTargetAdjuster, convert Aberrant to use it

* Always add targets for EachOpponentPermanentTargetsAdjuster

* Improve target name, finish Time Reaper

* Convert some cards

* Improve documentation, more cards

* More cards, fix cards that needed to use owner instead of controller

* Fix unfinished AlelaCunningConqueror changes

* more cards

* All remaining cards

* Fix target type

* Remove outdated attempt at TargetController.SOURCE_EFFECT_TARGET_POINTER

* Finish removal of SOURCE_EFFECT_TARGET_POINTER

* Change targetAdjuster blueprint target to be set inside setTargetAdjuster, add error checking

* Always add Target Adjuster after Target

* Add comment

* Fix TolarianContemptTest to skip opponent with no valid targets

* Forgot to git add the new abstract GenericTargetAdjuster

* Test now possible after merge, fix missed ChangeOfPlans adjuster order

* Text and optional-ness fixes

* Always set target pointer
This commit is contained in:
ssk97 2024-07-01 21:46:14 -07:00 committed by GitHub
parent bccf323c0f
commit 7cb669603f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
172 changed files with 891 additions and 2219 deletions

View file

@ -515,6 +515,10 @@ public interface Ability extends Controllable, Serializable {
boolean canFizzle();
/**
* Adds a target adjuster to this ability.
* If using a generic adjuster, only use after adding the blueprint target!
*/
Ability setTargetAdjuster(TargetAdjuster targetAdjuster);
TargetAdjuster getTargetAdjuster();

View file

@ -33,6 +33,7 @@ import mage.target.Target;
import mage.target.TargetCard;
import mage.target.Targets;
import mage.target.common.TargetCardInLibrary;
import mage.target.targetadjustment.GenericTargetAdjuster;
import mage.target.targetadjustment.TargetAdjuster;
import mage.util.CardUtil;
import mage.util.GameLog;
@ -1430,7 +1431,11 @@ public abstract class AbilityImpl implements Ability {
@Override
public AbilityImpl setTargetAdjuster(TargetAdjuster targetAdjuster) {
if (targetAdjuster instanceof GenericTargetAdjuster && this.getTargets().isEmpty()) {
throw new IllegalStateException("Target adjuster being added but no targets are set!");
}
this.targetAdjuster = targetAdjuster;
this.targetAdjuster.addDefaultTargets(this);
return this;
}

View file

@ -7,20 +7,29 @@ import mage.game.Game;
import mage.game.events.DamagedEvent;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.target.targetpointer.FixedTarget;
/**
* @author xenohedron
*/
public class DealsCombatDamageToAPlayerOrPlaneswalkerTriggeredAbility extends TriggeredAbilityImpl {
protected final boolean setTargetPointer;
public DealsCombatDamageToAPlayerOrPlaneswalkerTriggeredAbility(Effect effect, boolean optional) {
this(effect, optional, false);
}
public DealsCombatDamageToAPlayerOrPlaneswalkerTriggeredAbility(Effect effect, boolean optional, boolean setTargetPointer) {
super(Zone.BATTLEFIELD, effect, optional);
setTriggerPhrase("Whenever {this} deals combat damage to a player or planeswalker, ");
this.setTargetPointer = setTargetPointer;
}
protected DealsCombatDamageToAPlayerOrPlaneswalkerTriggeredAbility(final DealsCombatDamageToAPlayerOrPlaneswalkerTriggeredAbility ability) {
super(ability);
this.setTargetPointer = ability.setTargetPointer;
}
@Override
@ -47,6 +56,9 @@ public class DealsCombatDamageToAPlayerOrPlaneswalkerTriggeredAbility extends Tr
}
}
getAllEffects().setValue("damage", event.getAmount());
if (setTargetPointer) {
getAllEffects().setTargetPointer(new FixedTarget(event.getPlayerId()));
}
return true;
}

View file

@ -1,24 +1,23 @@
package mage.game.permanent.token;
import mage.MageInt;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.Ability;
import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility;
import mage.abilities.effects.common.continuous.GainControlTargetEffect;
import mage.abilities.keyword.FlyingAbility;
import mage.abilities.keyword.MenaceAbility;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.SubType;
import mage.constants.Zone;
import mage.filter.common.FilterArtifactPermanent;
import mage.filter.predicate.permanent.ControllerIdPredicate;
import mage.game.Game;
import mage.game.events.DamagedPlayerEvent;
import mage.game.events.GameEvent;
import mage.players.Player;
import mage.target.common.TargetArtifactPermanent;
import mage.target.TargetPermanent;
import mage.target.targetadjustment.DamagedPlayerControlsTargetAdjuster;
public class DragonMenaceAndStealArtifactToken extends TokenImpl {
private static final FilterArtifactPermanent filter
= new FilterArtifactPermanent("artifact that player controls");
public DragonMenaceAndStealArtifactToken() {
super("Dragon Token", "6/6 black and red Dragon creature token with flying, menace, and \"Whenever this creature deals combat damage to a player, gain control of target artifact that player controls.\"");
cardType.add(CardType.CREATURE);
@ -30,7 +29,10 @@ public class DragonMenaceAndStealArtifactToken extends TokenImpl {
addAbility(FlyingAbility.getInstance());
addAbility(new MenaceAbility(false));
addAbility(new DragonTokenTriggeredAbility());
Ability ability = new DealsCombatDamageToAPlayerTriggeredAbility(new GainControlTargetEffect(Duration.EndOfGame), false, true);
ability.addTarget(new TargetPermanent(filter));
ability.setTargetAdjuster(new DamagedPlayerControlsTargetAdjuster());
addAbility(ability);
}
private DragonMenaceAndStealArtifactToken(final DragonMenaceAndStealArtifactToken token) {
@ -42,48 +44,3 @@ public class DragonMenaceAndStealArtifactToken extends TokenImpl {
}
}
class DragonTokenTriggeredAbility extends TriggeredAbilityImpl {
public DragonTokenTriggeredAbility() {
super(Zone.BATTLEFIELD, new GainControlTargetEffect(Duration.EndOfGame));
this.addTarget(new TargetArtifactPermanent());
}
protected DragonTokenTriggeredAbility(final DragonTokenTriggeredAbility ability) {
super(ability);
}
@Override
public DragonTokenTriggeredAbility copy() {
return new DragonTokenTriggeredAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.DAMAGED_PLAYER;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
DamagedPlayerEvent damageEvent = (DamagedPlayerEvent) event;
if (damageEvent.isCombatDamage() && event.getSourceId().equals(this.getSourceId())) {
Player opponent = game.getPlayer(event.getPlayerId());
if (opponent != null) {
FilterArtifactPermanent filter = new FilterArtifactPermanent("artifact " + opponent.getLogName() + " controls");
filter.add(new ControllerIdPredicate(opponent.getId()));
this.getTargets().clear();
this.addTarget(new TargetArtifactPermanent(filter));
return true;
}
}
return false;
}
@Override
public String getRule() {
return "Whenever this creature deals combat damage to a player, gain control of target artifact that player controls.";
}
}

View file

@ -0,0 +1,61 @@
package mage.target.targetadjustment;
import mage.abilities.Ability;
import mage.filter.Filter;
import mage.filter.predicate.card.OwnerIdPredicate;
import mage.filter.predicate.permanent.ControllerIdPredicate;
import mage.game.Game;
import mage.players.Player;
import mage.target.Target;
import mage.target.targetpointer.FirstTargetPointer;
import mage.util.CardUtil;
import java.util.UUID;
/**
* @author notgreat
*/
public class DamagedPlayerControlsTargetAdjuster extends GenericTargetAdjuster {
private final boolean owner;
/**
* Use with {@link mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility} with setTargetPointer enabled,
* or {@link mage.abilities.common.OneOrMoreDealDamageTriggeredAbility} with "SetTargetPointer.PLAYER" or similar.
* Adjusts the target to only target something the damaged player controls (or owns with alternative constructor)
* And then removes the effects' target pointer that the triggered ability set
*/
public DamagedPlayerControlsTargetAdjuster() {
this(false);
}
public DamagedPlayerControlsTargetAdjuster(boolean owner) {
this.owner = owner;
}
@Override
public void addDefaultTargets(Ability ability) {
super.addDefaultTargets(ability);
CardUtil.AssertNoControllerOwnerPredicates(blueprintTarget);
}
@Override
public void adjustTargets(Ability ability, Game game) {
UUID opponentId = ability.getEffects().get(0).getTargetPointer().getFirst(game, ability);
Player opponent = game.getPlayer(opponentId);
ability.getTargets().clear();
ability.getAllEffects().setTargetPointer(new FirstTargetPointer());
if (opponent == null) {
return;
}
Target newTarget = blueprintTarget.copy();
Filter filter = newTarget.getFilter();
if (owner) {
filter.add(new OwnerIdPredicate(opponentId));
newTarget.withTargetName(filter.getMessage() + " (owned by " + opponent.getLogName() + ")");
} else {
filter.add(new ControllerIdPredicate(opponentId));
newTarget.withTargetName(filter.getMessage() + " (controlled by " + opponent.getLogName() + ")");
}
ability.addTarget(newTarget);
}
}

View file

@ -4,17 +4,18 @@ import mage.abilities.Ability;
import mage.filter.Filter;
import mage.filter.predicate.permanent.ControllerIdPredicate;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.Target;
import mage.target.TargetPermanent;
import mage.util.CardUtil;
import java.util.UUID;
/**
* @author notgreat
*/
public class EachOpponentPermanentTargetsAdjuster implements TargetAdjuster {
private TargetPermanent blueprintTarget = null;
public class EachOpponentPermanentTargetsAdjuster extends GenericTargetAdjuster {
/**
* Duplicates the permanent target for each opponent.
* Filtering of permanent's controllers will be handled inside, so
@ -24,23 +25,27 @@ public class EachOpponentPermanentTargetsAdjuster implements TargetAdjuster {
}
@Override
public void adjustTargets(Ability ability, Game game) {
if (blueprintTarget == null) {
blueprintTarget = (TargetPermanent) ability.getTargets().get(0).copy();
public void addDefaultTargets(Ability ability) {
super.addDefaultTargets(ability);
if (!(blueprintTarget instanceof TargetPermanent)) {
throw new IllegalArgumentException("EachOpponentPermanentTargetsAdjuster must use Permanent target - " + blueprintTarget);
}
CardUtil.AssertNoControllerOwnerPredicates(blueprintTarget);
}
@Override
public void adjustTargets(Ability ability, Game game) {
ability.getTargets().clear();
for (UUID opponentId : game.getOpponents(ability.getControllerId())) {
Player opponent = game.getPlayer(opponentId);
if (opponent == null) {
continue;
}
TargetPermanent newTarget = blueprintTarget.copy();
Filter<Permanent> filter = newTarget.getFilter();
Target newTarget = blueprintTarget.copy();
Filter filter = newTarget.getFilter();
filter.add(new ControllerIdPredicate(opponentId));
if (!newTarget.possibleTargets(ability.getControllerId(), ability, game).isEmpty()) {
newTarget.withTargetName(filter.getMessage() + " controlled by " + opponent.getLogName());
ability.addTarget(newTarget);
}
newTarget.withTargetName(filter.getMessage() + " controlled by " + opponent.getLogName());
ability.addTarget(newTarget);
}
}
}

View file

@ -0,0 +1,17 @@
package mage.target.targetadjustment;
import mage.abilities.Ability;
import mage.target.Target;
public abstract class GenericTargetAdjuster implements TargetAdjuster {
protected Target blueprintTarget = null;
@Override
public void addDefaultTargets(Ability ability) {
if (blueprintTarget == null) {
blueprintTarget = ability.getTargets().get(0).copy();
} else {
throw new IllegalStateException("Wrong code usage: target adjuster already has blueprint target - " + blueprintTarget);
}
}
}

View file

@ -12,8 +12,7 @@ import mage.target.Target;
/**
* @author TheElk801, notgreat
*/
public class ManaValueTargetAdjuster implements TargetAdjuster {
private Target blueprintTarget = null;
public class ManaValueTargetAdjuster extends GenericTargetAdjuster {
private final DynamicValue dynamicValue;
private final ComparisonType comparison;
@ -30,10 +29,6 @@ public class ManaValueTargetAdjuster implements TargetAdjuster {
@Override
public void adjustTargets(Ability ability, Game game) {
if (blueprintTarget == null) {
blueprintTarget = ability.getTargets().get(0).copy();
blueprintTarget.clearChosen();
}
Target newTarget = blueprintTarget.copy();
int amount = dynamicValue.calculate(game, ability, ability.getEffects().get(0));
Filter<MageObject> filter = newTarget.getFilter();

View file

@ -13,8 +13,7 @@ import mage.target.Target;
/**
* @author TheElk801, notgreat
*/
public class PowerTargetAdjuster implements TargetAdjuster {
private Target blueprintTarget = null;
public class PowerTargetAdjuster extends GenericTargetAdjuster {
private final DynamicValue dynamicValue;
private final ComparisonType comparison;
@ -33,12 +32,9 @@ public class PowerTargetAdjuster implements TargetAdjuster {
this(ManacostVariableValue.REGULAR, comparison);
}
@Override
public void adjustTargets(Ability ability, Game game) {
if (blueprintTarget == null) {
blueprintTarget = ability.getTargets().get(0).copy();
blueprintTarget.clearChosen();
}
Target newTarget = blueprintTarget.copy();
int amount = dynamicValue.calculate(game, ability, ability.getEffects().get(0));
Filter<MageObject> filter = newTarget.getFilter();

View file

@ -13,4 +13,10 @@ public interface TargetAdjuster extends Serializable {
// Warning: This is not Copyable, do not use changeable data inside (only use static objects like Filter)
void adjustTargets(Ability ability, Game game);
/**
* Add default blueprint target to the ability
*/
default void addDefaultTargets(Ability ability) {
}
}

View file

@ -9,8 +9,7 @@ import mage.target.Target;
/**
* @author TheElk801, notgreat
*/
public class TargetsCountAdjuster implements TargetAdjuster {
private Target blueprintTarget = null;
public class TargetsCountAdjuster extends GenericTargetAdjuster {
private final DynamicValue dynamicValue;
/**
@ -26,10 +25,6 @@ public class TargetsCountAdjuster implements TargetAdjuster {
@Override
public void adjustTargets(Ability ability, Game game) {
if (blueprintTarget == null) {
blueprintTarget = ability.getTargets().get(0).copy();
blueprintTarget.clearChosen();
}
Target newTarget = blueprintTarget.copy();
int count = dynamicValue.calculate(game, ability, ability.getEffects().get(0));
newTarget.setMaxNumberOfTargets(count);

View file

@ -13,8 +13,7 @@ import mage.target.Target;
/**
* @author TheElk801, notgreat
*/
public class ToughnessTargetAdjuster implements TargetAdjuster {
private Target blueprintTarget = null;
public class ToughnessTargetAdjuster extends GenericTargetAdjuster {
private final DynamicValue dynamicValue;
private final ComparisonType comparison;
@ -35,10 +34,6 @@ public class ToughnessTargetAdjuster implements TargetAdjuster {
@Override
public void adjustTargets(Ability ability, Game game) {
if (blueprintTarget == null) {
blueprintTarget = ability.getTargets().get(0).copy();
blueprintTarget.clearChosen();
}
Target newTarget = blueprintTarget.copy();
int amount = dynamicValue.calculate(game, ability, ability.getEffects().get(0));
Filter<MageObject> filter = newTarget.getFilter();

View file

@ -26,7 +26,11 @@ import mage.counters.Counter;
import mage.filter.Filter;
import mage.filter.FilterCard;
import mage.filter.StaticFilters;
import mage.filter.predicate.Predicate;
import mage.filter.predicate.Predicates;
import mage.filter.predicate.card.OwnerIdPredicate;
import mage.filter.predicate.mageobject.NamePredicate;
import mage.filter.predicate.permanent.ControllerIdPredicate;
import mage.game.CardState;
import mage.game.Game;
import mage.game.GameState;
@ -2079,6 +2083,15 @@ public final class CardUtil {
return stream.filter(clazz::isInstance).map(clazz::cast).filter(Objects::nonNull);
}
public static void AssertNoControllerOwnerPredicates(Target target) {
List<Predicate> list = new ArrayList<>();
Predicates.collectAllComponents(target.getFilter().getPredicates(), target.getFilter().getExtraPredicates(), list);
if (list.stream().anyMatch(p -> p instanceof TargetController.ControllerPredicate || p instanceof TargetController.OwnerPredicate
|| p instanceof OwnerIdPredicate || p instanceof ControllerIdPredicate)) {
throw new IllegalArgumentException("Wrong code usage: target adjuster will add controller/owner predicate, but target's filter already has one - " + target);
}
}
/**
* Move card or permanent to dest zone and add counter to it
*