Rework "second main phase" abilities (#12970)

* replace all instances of "postcombat main phase" with "second main phase" minus exceptions

* second main trigger now works correctly

* add survival test

* additional text fixes
This commit is contained in:
Evan Kranzler 2024-10-18 23:03:43 -04:00 committed by GitHub
parent 3131365abd
commit 1484e3b575
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
39 changed files with 339 additions and 115 deletions

View file

@ -1,28 +1,26 @@
package mage.abilities.abilityword;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.common.BeginningOfSecondMainTriggeredAbility;
import mage.abilities.effects.Effect;
import mage.constants.AbilityWord;
import mage.constants.TargetController;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import java.util.Optional;
/**
* TODO: This should only trigger on the second main phase, this is part of a larger refactor that has to be done
*
* @author TheElk801
*/
public class SurvivalAbility extends TriggeredAbilityImpl {
public class SurvivalAbility extends BeginningOfSecondMainTriggeredAbility {
public SurvivalAbility(Effect effect) {
this(effect, false);
}
public SurvivalAbility(Effect effect, boolean optional) {
super(Zone.BATTLEFIELD, effect, optional);
super(Zone.BATTLEFIELD, effect, TargetController.YOU, optional, false);
setTriggerPhrase("At the beginning of your second main phase, if {this} is tapped, ");
setAbilityWord(AbilityWord.SURVIVAL);
}
@ -36,20 +34,10 @@ public class SurvivalAbility extends TriggeredAbilityImpl {
return new SurvivalAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.POSTCOMBAT_MAIN_PHASE_PRE;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
return game.isActivePlayer(getControllerId());
}
@Override
public boolean checkInterveningIfClause(Game game) {
return Optional
.ofNullable(getSourcePermanentIfItStillExists(game))
.ofNullable(getSourcePermanentOrLKI(game))
.map(Permanent::isTapped)
.orElse(false);
}

View file

@ -12,31 +12,31 @@ import mage.target.targetpointer.FixedTarget;
* @author LevelX2
*/
public class BeginningOfPreCombatMainTriggeredAbility extends TriggeredAbilityImpl {
public class BeginningOfFirstMainTriggeredAbility extends TriggeredAbilityImpl {
private TargetController targetController;
private boolean setTargetPointer;
public BeginningOfPreCombatMainTriggeredAbility(Effect effect, TargetController targetController, boolean isOptional) {
public BeginningOfFirstMainTriggeredAbility(Effect effect, TargetController targetController, boolean isOptional) {
this(Zone.BATTLEFIELD, effect, targetController, isOptional, false);
}
public BeginningOfPreCombatMainTriggeredAbility(Zone zone, Effect effect, TargetController targetController, boolean isOptional, boolean setTargetPointer) {
public BeginningOfFirstMainTriggeredAbility(Zone zone, Effect effect, TargetController targetController, boolean isOptional, boolean setTargetPointer) {
super(zone, effect, isOptional);
this.targetController = targetController;
this.setTargetPointer = setTargetPointer;
setTriggerPhrase(generateTriggerPhrase());
}
protected BeginningOfPreCombatMainTriggeredAbility(final BeginningOfPreCombatMainTriggeredAbility ability) {
protected BeginningOfFirstMainTriggeredAbility(final BeginningOfFirstMainTriggeredAbility ability) {
super(ability);
this.targetController = ability.targetController;
this.setTargetPointer = ability.setTargetPointer;
}
@Override
public BeginningOfPreCombatMainTriggeredAbility copy() {
return new BeginningOfPreCombatMainTriggeredAbility(this);
public BeginningOfFirstMainTriggeredAbility copy() {
return new BeginningOfFirstMainTriggeredAbility(this);
}
@Override
@ -81,11 +81,11 @@ public class BeginningOfPreCombatMainTriggeredAbility extends TriggeredAbilityIm
private String generateTriggerPhrase() {
switch (targetController) {
case YOU:
return "At the beginning of your precombat main phase, " + generateZoneString();
return "At the beginning of your first main phase, " + generateZoneString();
case OPPONENT:
return "At the beginning of each opponent's precombat main phase, " + generateZoneString();
return "At the beginning of each opponent's first main phase, " + generateZoneString();
case ANY:
return "At the beginning of each player's precombat main phase, " + generateZoneString();
return "At the beginning of each player's first main phase, " + generateZoneString();
}
return "";
}

View file

@ -48,25 +48,25 @@ public class BeginningOfPostCombatMainTriggeredAbility extends TriggeredAbilityI
public boolean checkTrigger(GameEvent event, Game game) {
switch (targetController) {
case YOU:
boolean yours = event.getPlayerId().equals(this.controllerId);
if (yours && setTargetPointer) {
if (getTargets().isEmpty()) {
for (Effect effect : this.getEffects()) {
effect.setTargetPointer(new FixedTarget(event.getPlayerId()));
}
if (!isControlledBy(event.getPlayerId())) {
return false;
}
if (setTargetPointer && getTargets().isEmpty()) {
for (Effect effect : this.getEffects()) {
effect.setTargetPointer(new FixedTarget(event.getPlayerId()));
}
}
return yours;
return true;
case OPPONENT:
if (game.getPlayer(this.controllerId).hasOpponent(event.getPlayerId(), game)) {
if (setTargetPointer) {
for (Effect effect : this.getEffects()) {
effect.setTargetPointer(new FixedTarget(event.getPlayerId()));
}
}
return true;
if (!game.getOpponents(this.controllerId).contains(event.getPlayerId())) {
return false;
}
break;
if (setTargetPointer) {
for (Effect effect : this.getEffects()) {
effect.setTargetPointer(new FixedTarget(event.getPlayerId()));
}
}
return true;
case ANY:
if (setTargetPointer) {
for (Effect effect : this.getEffects()) {
@ -77,7 +77,7 @@ public class BeginningOfPostCombatMainTriggeredAbility extends TriggeredAbilityI
case ENCHANTED:
Permanent permanent = getSourcePermanentIfItStillExists(game);
if (permanent == null || !game.isActivePlayer(permanent.getAttachedTo())) {
break;
return false;
}
if (getTargets().isEmpty()) {
this.getEffects().setTargetPointer(new FixedTarget(event.getPlayerId()));
@ -90,13 +90,13 @@ public class BeginningOfPostCombatMainTriggeredAbility extends TriggeredAbilityI
private String generateTriggerPhrase() {
switch (targetController) {
case YOU:
return "At the beginning of your postcombat main phase, " + generateZoneString();
return "At the beginning of each of your postcombat main phases, " + generateZoneString();
case OPPONENT:
return "At the beginning of each opponent's postcombat main phase, " + generateZoneString();
return "At the beginning of each of your opponent's postcombat main phases, " + generateZoneString();
case ANY:
return "At the beginning of each player's postcombat main phase, " + generateZoneString();
return "At the beginning of each postcombat main phase, " + generateZoneString();
case ENCHANTED:
return "At the beginning of enchanted player's postcombat main phase, " + generateZoneString();
return "At the beginning of each of enchanted player's postcombat main phases, " + generateZoneString();
}
return "";
}

View file

@ -0,0 +1,153 @@
package mage.abilities.common;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.Effect;
import mage.constants.TargetController;
import mage.constants.WatcherScope;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.target.targetpointer.FixedTarget;
import mage.watchers.Watcher;
/**
* @author TheElk801
*/
public class BeginningOfSecondMainTriggeredAbility extends TriggeredAbilityImpl {
private TargetController targetController;
private boolean setTargetPointer;
public BeginningOfSecondMainTriggeredAbility(Effect effect, TargetController targetController, boolean isOptional) {
this(Zone.BATTLEFIELD, effect, targetController, isOptional, false);
}
public BeginningOfSecondMainTriggeredAbility(Zone zone, Effect effect, TargetController targetController, boolean isOptional, boolean setTargetPointer) {
super(zone, effect, isOptional);
this.targetController = targetController;
this.setTargetPointer = setTargetPointer;
setTriggerPhrase(generateTriggerPhrase());
this.addWatcher(new MainPhaseWatcher());
}
protected BeginningOfSecondMainTriggeredAbility(final BeginningOfSecondMainTriggeredAbility ability) {
super(ability);
this.targetController = ability.targetController;
this.setTargetPointer = ability.setTargetPointer;
}
@Override
public BeginningOfSecondMainTriggeredAbility copy() {
return new BeginningOfSecondMainTriggeredAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
switch (event.getType()) {
case PRECOMBAT_MAIN_PHASE_PRE:
case POSTCOMBAT_MAIN_PHASE_PRE:
return true;
}
return false;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
if (!MainPhaseWatcher.checkCount(game)) {
return false;
}
switch (targetController) {
case YOU:
if (!isControlledBy(event.getPlayerId())) {
return false;
}
if (setTargetPointer && getTargets().isEmpty()) {
for (Effect effect : this.getEffects()) {
effect.setTargetPointer(new FixedTarget(event.getPlayerId()));
}
}
return true;
case OPPONENT:
if (!game.getOpponents(this.controllerId).contains(event.getPlayerId())) {
return false;
}
if (setTargetPointer) {
for (Effect effect : this.getEffects()) {
effect.setTargetPointer(new FixedTarget(event.getPlayerId()));
}
}
return true;
case ANY:
if (setTargetPointer) {
for (Effect effect : this.getEffects()) {
effect.setTargetPointer(new FixedTarget(event.getPlayerId()));
}
}
return true;
case ENCHANTED:
Permanent permanent = getSourcePermanentIfItStillExists(game);
if (permanent == null || !game.isActivePlayer(permanent.getAttachedTo())) {
return false;
}
if (getTargets().isEmpty()) {
this.getEffects().setTargetPointer(new FixedTarget(event.getPlayerId()));
}
return true;
}
return false;
}
private String generateTriggerPhrase() {
switch (targetController) {
case YOU:
return "At the beginning of your second main phase, " + generateZoneString();
case OPPONENT:
return "At the beginning of each opponent's second main phase, " + generateZoneString();
case ANY:
return "At the beginning of each player's second main phase, " + generateZoneString();
case ENCHANTED:
return "At the beginning of enchanted player's second main phase, " + generateZoneString();
}
return "";
}
private String generateZoneString() {
switch (getZone()) {
case GRAVEYARD:
return "if {this} is in your graveyard, ";
}
return "";
}
}
class MainPhaseWatcher extends Watcher {
private int mainPhaseCount = 0;
MainPhaseWatcher() {
super(WatcherScope.GAME);
}
@Override
public void watch(GameEvent event, Game game) {
switch (event.getType()) {
case PRECOMBAT_MAIN_PHASE_PRE:
case POSTCOMBAT_MAIN_PHASE_PRE:
mainPhaseCount++;
}
}
@Override
public void reset() {
super.reset();
this.mainPhaseCount = 0;
}
static boolean checkCount(Game game) {
return game
.getState()
.getWatcher(MainPhaseWatcher.class)
.mainPhaseCount == 2;
}
}

View file

@ -17,7 +17,7 @@ public class AtTheBeginOfMainPhaseDelayedTriggeredAbility extends DelayedTrigger
public enum PhaseSelection {
NEXT_PRECOMBAT_MAIN("next precombat main phase"),
NEXT_PRECOMBAT_MAIN("next first main phase"),
NEXT_POSTCOMBAT_MAIN("next postcombat main phase"),
NEXT_MAIN("next main phase"),
NEXT_MAIN_THIS_TURN("next main phase this turn", Duration.EndOfTurn);

View file

@ -1,7 +1,7 @@
package mage.game.command.emblems;
import mage.abilities.Ability;
import mage.abilities.common.BeginningOfPreCombatMainTriggeredAbility;
import mage.abilities.common.BeginningOfFirstMainTriggeredAbility;
import mage.abilities.condition.Condition;
import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility;
import mage.abilities.effects.OneShotEffect;
@ -31,7 +31,7 @@ public class RadiationEmblem extends Emblem {
this.frameStyle = FrameStyle.M15_NORMAL;
this.getAbilities().add(new ConditionalInterveningIfTriggeredAbility(
new BeginningOfPreCombatMainTriggeredAbility(Zone.ALL, new RadiationEffect(), TargetController.YOU, false, false),
new BeginningOfFirstMainTriggeredAbility(Zone.ALL, new RadiationEffect(), TargetController.YOU, false, false),
RadiationCondition.instance,
"At the beginning of your precombat main phase, if you have any rad counters, "
+ "mill that many cards. For each nonland card milled this way, you lose 1 life and a rad counter."
@ -112,4 +112,4 @@ class RadiationEffect extends OneShotEffect {
}
return true;
}
}
}