Create common class for "when target dies this turn" delayed triggers (#9722)

Fixes #9716. Fixes #9719.
This commit is contained in:
Alex W. Jackson 2022-11-04 05:27:56 -04:00 committed by GitHub
parent afb7d75007
commit 10d7acb4b5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
74 changed files with 397 additions and 1628 deletions

View file

@ -56,14 +56,7 @@ public abstract class DelayedTriggeredAbility extends TriggeredAbilityImpl {
*
* @param game
*/
public void initOnAdding(Game game) {
}
public void init(Game game) {
for (Effect effect : this.getEffects()) {
effect.getTargetPointer().init(game, this);
}
}
public boolean isInactive(Game game) {

View file

@ -0,0 +1,84 @@
package mage.abilities.common.delayed;
import mage.MageObjectReference;
import mage.abilities.DelayedTriggeredAbility;
import mage.abilities.effects.Effect;
import mage.constants.Duration;
import mage.constants.SetTargetPointer;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.ZoneChangeEvent;
import mage.game.permanent.Permanent;
import mage.target.targetpointer.FixedTarget;
/**
* @author awjackson
*/
public class WhenTargetDiesDelayedTriggeredAbility extends DelayedTriggeredAbility {
protected MageObjectReference mor;
private final SetTargetPointer setTargetPointer;
public WhenTargetDiesDelayedTriggeredAbility(Effect effect) {
this(effect, SetTargetPointer.NONE);
}
public WhenTargetDiesDelayedTriggeredAbility(Effect effect, SetTargetPointer setTargetPointer) {
this(effect, Duration.EndOfTurn, setTargetPointer);
}
public WhenTargetDiesDelayedTriggeredAbility(Effect effect, Duration duration, SetTargetPointer setTargetPointer) {
super(effect, duration, true);
this.setTargetPointer = setTargetPointer;
setTriggerPhrase("When that creature dies" + (duration == Duration.EndOfTurn ? " this turn, " : ", "));
}
public WhenTargetDiesDelayedTriggeredAbility(final WhenTargetDiesDelayedTriggeredAbility ability) {
super(ability);
this.mor = ability.mor;
this.setTargetPointer = ability.setTargetPointer;
}
@Override
public WhenTargetDiesDelayedTriggeredAbility copy() {
return new WhenTargetDiesDelayedTriggeredAbility(this);
}
@Override
public void init(Game game) {
mor = new MageObjectReference(getFirstTarget(), game);
getTargets().clear();
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.ZONE_CHANGE;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
ZoneChangeEvent zEvent = (ZoneChangeEvent) event;
if (!zEvent.isDiesEvent()) {
return false;
}
Permanent permanent = zEvent.getTarget();
if (mor == null || !mor.refersTo(permanent, game)) {
return false;
}
switch (setTargetPointer) {
case CARD:
// card in graveyard
getEffects().setTargetPointer(new FixedTarget(event.getTargetId(), game));
break;
case PERMANENT:
// LKI of permanent
getEffects().setTargetPointer(new FixedTarget(permanent.getId(), permanent.getZoneChangeCounter(game)));
break;
case PLAYER:
// LKI permanent's controller
getEffects().setTargetPointer(new FixedTarget(permanent.getControllerId()));
break;
}
return true;
}
}

View file

@ -14,28 +14,28 @@ public class CreateDelayedTriggeredAbilityEffect extends OneShotEffect {
protected DelayedTriggeredAbility ability;
protected boolean copyTargets;
protected boolean initAbility;
protected String rulePrefix;
public CreateDelayedTriggeredAbilityEffect(DelayedTriggeredAbility ability) {
this(ability, true);
}
public CreateDelayedTriggeredAbilityEffect(DelayedTriggeredAbility ability, boolean copyTargets) {
this(ability, copyTargets, false);
this(ability, copyTargets, "");
}
public CreateDelayedTriggeredAbilityEffect(DelayedTriggeredAbility ability, boolean copyTargets, boolean initAbility) {
public CreateDelayedTriggeredAbilityEffect(DelayedTriggeredAbility ability, boolean copyTargets, String rulePrefix) {
super(ability.getEffects().getOutcome(ability));
this.ability = ability;
this.copyTargets = copyTargets;
this.initAbility = initAbility;
this.rulePrefix = rulePrefix;
}
public CreateDelayedTriggeredAbilityEffect(final CreateDelayedTriggeredAbilityEffect effect) {
super(effect);
this.ability = effect.ability.copy();
this.copyTargets = effect.copyTargets;
this.initAbility = effect.initAbility;
this.rulePrefix = effect.rulePrefix;
}
@Override
@ -56,9 +56,6 @@ public class CreateDelayedTriggeredAbilityEffect extends OneShotEffect {
}
}
}
if (initAbility) {
delayedAbility.init(game);
}
game.addDelayedTriggeredAbility(delayedAbility, source);
return true;
}
@ -69,7 +66,7 @@ public class CreateDelayedTriggeredAbilityEffect extends OneShotEffect {
return staticText;
}
if (ability.getRuleVisible()) {
return ability.getRule();
return rulePrefix + ability.getRule();
} else {
return "";
}

View file

@ -20,8 +20,6 @@ public class ReturnToBattlefieldUnderOwnerControlTargetEffect extends OneShotEff
private boolean tapped;
protected boolean returnFromExileZoneOnly;
private String returnName = "that card";
private String returnUnderControlName = "its owner's";
/**
* @param returnFromExileZoneOnly see https://github.com/magefree/mage/issues/5151
@ -29,28 +27,20 @@ public class ReturnToBattlefieldUnderOwnerControlTargetEffect extends OneShotEff
* return exiled card - true
*/
public ReturnToBattlefieldUnderOwnerControlTargetEffect(boolean tapped, boolean returnFromExileZoneOnly) {
this(tapped, returnFromExileZoneOnly, "that card");
}
public ReturnToBattlefieldUnderOwnerControlTargetEffect(boolean tapped, boolean returnFromExileZoneOnly, String description) {
super(Outcome.Benefit);
this.tapped = tapped;
this.returnFromExileZoneOnly = returnFromExileZoneOnly;
updateText();
staticText = "return " + description + " to the battlefield " + (tapped ? "tapped " : "") + "under its owner's control";
}
public ReturnToBattlefieldUnderOwnerControlTargetEffect(final ReturnToBattlefieldUnderOwnerControlTargetEffect effect) {
super(effect);
this.tapped = effect.tapped;
this.returnFromExileZoneOnly = effect.returnFromExileZoneOnly;
this.returnName = effect.returnName;
this.returnUnderControlName = effect.returnUnderControlName;
updateText();
}
// TODO: This does not generate correct text, the ", then" should be from .concatBy, see Angelic Renewal for a card with incorrect text
private void updateText() {
this.staticText = ", then return " + this.returnName
+ " to the battlefield" + (tapped ? " tapped" : "")
+ " under " + this.returnUnderControlName + " control";
}
@Override
@ -92,11 +82,4 @@ public class ReturnToBattlefieldUnderOwnerControlTargetEffect extends OneShotEff
}
return false;
}
public ReturnToBattlefieldUnderOwnerControlTargetEffect withReturnNames(String returnName, String returnUnderControlName) {
this.returnName = returnName;
this.returnUnderControlName = returnUnderControlName;
updateText();
return this;
}
}

View file

@ -20,17 +20,13 @@ import java.util.UUID;
public class ReturnToBattlefieldUnderYourControlTargetEffect extends OneShotEffect {
private boolean returnFromExileZoneOnly;
private boolean tapped;
private boolean attacking;
private String returnName = "that card";
private String returnUnderControlName = "your";
public ReturnToBattlefieldUnderYourControlTargetEffect() {
this(false);
}
public ReturnToBattlefieldUnderYourControlTargetEffect(boolean returnFromExileZoneOnly) {
this(returnFromExileZoneOnly, false, false);
this(returnFromExileZoneOnly, "that card");
}
/**
@ -38,31 +34,15 @@ public class ReturnToBattlefieldUnderYourControlTargetEffect extends OneShotEffe
* return it or that card - false
* return exiled card - true
*/
public ReturnToBattlefieldUnderYourControlTargetEffect(boolean returnFromExileZoneOnly, boolean tapped, boolean attacking) {
public ReturnToBattlefieldUnderYourControlTargetEffect(boolean returnFromExileZoneOnly, String description) {
super(Outcome.Benefit);
this.returnFromExileZoneOnly = returnFromExileZoneOnly;
this.tapped = tapped;
this.attacking = attacking;
updateText();
staticText = "return " + description + " to the battlefield under your control";
}
public ReturnToBattlefieldUnderYourControlTargetEffect(final ReturnToBattlefieldUnderYourControlTargetEffect effect) {
super(effect);
this.returnFromExileZoneOnly = effect.returnFromExileZoneOnly;
this.tapped = effect.tapped;
this.attacking = effect.attacking;
this.returnName = effect.returnName;
this.returnUnderControlName = effect.returnUnderControlName;
updateText();
}
private void updateText() {
this.staticText = ", then return " + returnName + " to the battlefield under " + returnUnderControlName + " control"
+ (tapped ? " tapped" : "")
+ (tapped && attacking ? " and" : "")
+ (attacking ? " attacking" : "");
}
@Override
@ -98,23 +78,11 @@ public class ReturnToBattlefieldUnderYourControlTargetEffect extends OneShotEffe
} else {
cardsToBattlefield.addAll(getTargetPointer().getTargets(game, source));
}
for (Card card : cardsToBattlefield.getCards(game)) {
if (controller.moveCards(card, Zone.BATTLEFIELD, source, game, tapped, false, false, null)) {
if (attacking) {
game.getCombat().addAttackingCreature(card.getId(), game);
}
}
if (!cardsToBattlefield.isEmpty()) {
controller.moveCards(cardsToBattlefield.getCards(game), Zone.BATTLEFIELD, source, game, false, false, false, null);
}
return true;
}
return false;
}
public ReturnToBattlefieldUnderYourControlTargetEffect withReturnNames(String returnName, String returnUnderControlName) {
this.returnName = returnName;
this.returnUnderControlName = returnUnderControlName;
updateText();
return this;
}
}

View file

@ -28,7 +28,7 @@ public class AddCountersControllerEffect extends OneShotEffect {
super(Outcome.Benefit);
this.counter = counter.copy();
this.enchantedEquipped = enchantedEquipped;
staticText = "its controller gets " + counter.getDescription();
staticText = (enchantedEquipped ? "its controller gets " : "you get ") + counter.getDescription();
}
public AddCountersControllerEffect(final AddCountersControllerEffect effect) {

View file

@ -1985,10 +1985,7 @@ public abstract class GameImpl implements Game {
newAbility.setSourceObjectZoneChangeCounter(getState().getZoneChangeCounter(source.getSourceId()));
newAbility.setSourcePermanentTransformCount(this);
}
newAbility.initOnAdding(this);
// ability.init is called as the ability triggeres not now.
// If a FixedTarget pointer is already set from the effect setting up this delayed ability
// it has to be already initialized so it won't be overwitten as the ability triggers
newAbility.init(this);
getState().addDelayedTriggeredAbility(newAbility);
return newAbility.getId();
}