Implemented "Until your next end step" duration (#8831)

* initial implementation of until next end step duration

* added test, reworked effect duration
This commit is contained in:
Evan Kranzler 2022-04-10 17:57:58 -04:00 committed by GitHub
parent 1807565ef0
commit 6e65db284c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 160 additions and 90 deletions

View file

@ -67,6 +67,8 @@ public interface ContinuousEffect extends Effect {
boolean isYourNextTurn(Game game);
boolean isYourNextEndStep(Game game);
@Override
void newId();

View file

@ -4,7 +4,6 @@ import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.CompoundAbility;
import mage.abilities.MageSingleton;
import mage.abilities.costs.mana.ActivationManaAbilityStep;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.dynamicvalue.common.DomainValue;
import mage.abilities.dynamicvalue.common.SignInversionDynamicValue;
@ -19,6 +18,7 @@ import mage.game.stack.Spell;
import mage.game.stack.StackObject;
import mage.players.Player;
import mage.target.targetpointer.TargetPointer;
import mage.watchers.common.EndStepCountWatcher;
import java.util.*;
@ -61,6 +61,7 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu
private UUID startingControllerId; // player to check for turn duration (can't different with real controller ability)
private boolean startingTurnWasActive; // effect started during related players turn and related players turn was already active
private int effectStartingOnTurn = 0; // turn the effect started
private int effectStartingEndStep = 0;
public ContinuousEffectImpl(Duration duration, Outcome outcome) {
super(outcome);
@ -91,6 +92,7 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu
this.startingControllerId = effect.startingControllerId;
this.startingTurnWasActive = effect.startingTurnWasActive;
this.effectStartingOnTurn = effect.effectStartingOnTurn;
this.effectStartingEndStep = effect.effectStartingEndStep;
this.dependencyTypes = effect.dependencyTypes;
this.dependendToTypes = effect.dependendToTypes;
this.characterDefining = effect.characterDefining;
@ -211,6 +213,7 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu
this.startingTurnWasActive = activePlayerId != null
&& activePlayerId.equals(startingController); // you can't use "game" for active player cause it's called from tests/cheat too
this.effectStartingOnTurn = game.getTurnNum();
this.effectStartingEndStep = EndStepCountWatcher.getCount(startingController, game);
}
@Override
@ -219,6 +222,11 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu
&& game.isActivePlayer(startingControllerId);
}
@Override
public boolean isYourNextEndStep(Game game) {
return EndStepCountWatcher.getCount(startingControllerId, game) > effectStartingEndStep;
}
@Override
public boolean isInactive(Ability source, Game game) {
// YOUR turn checks
@ -227,6 +235,7 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu
switch (duration) {
case UntilYourNextTurn:
case UntilEndOfYourNextTurn:
case UntilYourNextEndStep:
break;
default:
return false;
@ -237,7 +246,7 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu
return false;
}
boolean canDelete = false;
boolean canDelete;
Player player = game.getPlayer(startingControllerId);
// discard on start of turn for leaved player
@ -247,18 +256,26 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu
switch (duration) {
case UntilYourNextTurn:
case UntilEndOfYourNextTurn:
canDelete = player == null
|| (!player.isInGame()
&& player.hasReachedNextTurnAfterLeaving());
canDelete = player == null || (!player.isInGame() && player.hasReachedNextTurnAfterLeaving());
break;
default:
canDelete = false;
}
if (canDelete) {
return true;
}
// discard on another conditions (start of your turn)
switch (duration) {
case UntilYourNextTurn:
if (player != null
&& player.isInGame()) {
canDelete = canDelete
|| this.isYourNextTurn(game);
if (player != null && player.isInGame()) {
return this.isYourNextTurn(game);
}
break;
case UntilYourNextEndStep:
if (player != null && player.isInGame()) {
return this.isYourNextEndStep(game);
}
}

View file

@ -50,7 +50,7 @@ public class ContinuousEffectsList<T extends ContinuousEffect> extends ArrayList
// rules 514.2
for (Iterator<T> i = this.iterator(); i.hasNext(); ) {
T entry = i.next();
boolean canRemove = false;
boolean canRemove;
switch (entry.getDuration()) {
case EndOfTurn:
canRemove = true;
@ -58,6 +58,11 @@ public class ContinuousEffectsList<T extends ContinuousEffect> extends ArrayList
case UntilEndOfYourNextTurn:
canRemove = entry.isYourNextTurn(game);
break;
case UntilYourNextEndStep:
canRemove = entry.isYourNextEndStep(game);
break;
default:
canRemove = false;
}
if (canRemove) {
i.remove();
@ -149,6 +154,7 @@ public class ContinuousEffectsList<T extends ContinuousEffect> extends ArrayList
case Custom:
case UntilYourNextTurn:
case UntilEndOfYourNextTurn:
case UntilYourNextEndStep:
// until your turn effects continue until real turn reached, their used it's own inactive method
// 514.2 Second, the following actions happen simultaneously: all damage marked on permanents
// (including phased-out permanents) is removed and all "until end of turn" and "this turn" effects end.

View file

@ -12,6 +12,7 @@ public enum Duration {
WhileInGraveyard("", false, false),
EndOfTurn("until end of turn", true, true),
UntilYourNextTurn("until your next turn", true, true),
UntilYourNextEndStep("until your next end step", true, true),
UntilEndOfYourNextTurn("until the end of your next turn", true, true),
UntilSourceLeavesBattlefield("until {this} leaves the battlefield", true, false), // supported for continuous layered effects
EndOfCombat("until end of combat", true, true),

View file

@ -1299,6 +1299,7 @@ public abstract class GameImpl implements Game {
newWatchers.add(new ManaSpentToCastWatcher());
newWatchers.add(new ManaPaidSourceWatcher());
newWatchers.add(new BlockingOrBlockedWatcher());
newWatchers.add(new EndStepCountWatcher());
newWatchers.add(new CommanderPlaysCountWatcher()); // commander plays count uses in non commander games by some cards
// runtime check - allows only GAME scope (one watcher per game)

View file

@ -0,0 +1,38 @@
package mage.watchers.common;
import mage.constants.WatcherScope;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.util.CardUtil;
import mage.watchers.Watcher;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* @author TheElk801
*/
public class EndStepCountWatcher extends Watcher {
private final Map<UUID, Integer> playerMap = new HashMap<>();
public EndStepCountWatcher() {
super(WatcherScope.GAME);
}
@Override
public void watch(GameEvent event, Game game) {
if (event.getType() == GameEvent.EventType.END_TURN_STEP_PRE) {
playerMap.compute(game.getActivePlayerId(), CardUtil::setOrIncrementValue);
}
}
public static int getCount(UUID playerId, Game game) {
return game
.getState()
.getWatcher(EndStepCountWatcher.class)
.playerMap
.getOrDefault(playerId, 0);
}
}