mirror of
https://github.com/magefree/mage.git
synced 2025-12-28 22:42:03 -08:00
[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:
parent
bccf323c0f
commit
7cb669603f
172 changed files with 891 additions and 2219 deletions
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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.";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue