Implement CR 800.4m (#189)

* Relocate effects instead of exiling
This commit is contained in:
tool4ever
2024-08-21 18:14:13 +00:00
committed by GitHub
parent 38fb16c64f
commit 064512ed83
6 changed files with 63 additions and 16 deletions

View File

@@ -875,17 +875,23 @@ public class Game {
// unattach all "Enchant Player"
c.removeAttachedTo(p);
if (c.getOwner().equals(p)) {
for (Card cc : cards) {
cc.removeImprintedCard(c);
cc.removeEncodedCard(c);
cc.removeRemembered(c);
cc.removeAttachedTo(c);
cc.removeAttachedCard(c);
if (c.getEffectSource() != null && !c.isEmblem()) {
// move effect to another player so they continue to work
c.getZone().remove(c);
getNextPlayerAfter(p).getZone(ZoneType.Command).add(c);
} else {
for (Card cc : cards) {
cc.removeImprintedCard(c);
cc.removeEncodedCard(c);
cc.removeRemembered(c);
cc.removeAttachedTo(c);
cc.removeAttachedCard(c);
}
triggerList.put(c.getZone().getZoneType(), null, c);
getAction().ceaseToExist(c, false);
// CR 603.2f owner of trigger source lost game
getTriggerHandler().clearDelayedTrigger(c);
}
triggerList.put(c.getZone().getZoneType(), null, c);
getAction().ceaseToExist(c, false);
// CR 603.2f owner of trigger source lost game
getTriggerHandler().clearDelayedTrigger(c);
} else {
// return stolen permanents
if (c.isInPlay() && (c.getController().equals(p) || c.getZone().getPlayer().equals(p))) {

View File

@@ -487,8 +487,7 @@ public abstract class SpellAbilityEffect {
String effect = "DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile";
final Trigger parsedTrigger = TriggerHandler.parseTrigger(trig, card, true);
parsedTrigger.setOverridingAbility(AbilityFactory.getAbility(effect, card));
final Trigger addedTrigger = card.addTrigger(parsedTrigger);
addedTrigger.setIntrinsic(true);
card.addTrigger(parsedTrigger);
}
protected static void addExileOnCounteredTrigger(final Card card) {
@@ -496,8 +495,7 @@ public abstract class SpellAbilityEffect {
String effect = "DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile";
final Trigger parsedTrigger = TriggerHandler.parseTrigger(trig, card, true);
parsedTrigger.setOverridingAbility(AbilityFactory.getAbility(effect, card));
final Trigger addedTrigger = card.addTrigger(parsedTrigger);
addedTrigger.setIntrinsic(true);
card.addTrigger(parsedTrigger);
}
protected static void addForgetOnPhasedInTrigger(final Card card) {
@@ -522,6 +520,14 @@ public abstract class SpellAbilityEffect {
card.addTrigger(parsedTrigger2);
}
protected static void addExileOnLostTrigger(final Card card) {
String trig = "Mode$ LosesGame | ValidPlayer$ You | TriggerController$ Player | TriggerZones$ Command | Static$ True";
String effect = "DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile";
final Trigger parsedTrigger = TriggerHandler.parseTrigger(trig, card, true);
parsedTrigger.setOverridingAbility(AbilityFactory.getAbility(effect, card));
card.addTrigger(parsedTrigger);
}
protected static void addLeaveBattlefieldReplacement(final Card card, final SpellAbility sa, final String zone) {
final Card host = sa.getHostCard();
final Game game = card.getGame();

View File

@@ -247,6 +247,10 @@ public class EffectEffect extends SpellAbilityEffect {
addForgetOnCastTrigger(eff, sa.getParam("ForgetOnCast"));
}
if (sa.hasParam("ExileOnLost")) {
addExileOnLostTrigger(eff);
}
// Set Imprinted
if (effectImprinted != null) {
eff.addImprintedCards(AbilityUtils.getDefinedCards(hostCard, effectImprinted, sa));

View File

@@ -509,6 +509,8 @@ public class PhaseHandler implements java.io.Serializable {
// done this after check state effects, so it only has effect next check
game.getCleanup().executeUntil(playerTurn);
handleMultiplayerEffects();
// "Trigger" for begin turn to get around a phase skipping
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromPlayer(playerTurn);
game.getTriggerHandler().runTrigger(TriggerType.TurnBegin, runParams, false);
@@ -1267,4 +1269,33 @@ public class PhaseHandler implements java.io.Serializable {
}
return count;
}
private void handleMultiplayerEffects() {
// CR 800.4m When a player leaves the game, any continuous effects with durations that last until that
// players next turn or until a specific point in that turn will last until that turn would have begun
int oldPlayerIdx = game.getRegisteredPlayers().indexOf(playerPreviousTurn);
final int playerIdx = game.getRegisteredPlayers().indexOf(playerTurn);
final int direction = game.getTurnOrder().getShift();
while (oldPlayerIdx != playerIdx) {
oldPlayerIdx += direction;
if (oldPlayerIdx < 0) {
oldPlayerIdx = game.getRegisteredPlayers().size() - 1;
} else if (oldPlayerIdx > game.getRegisteredPlayers().size() - 1) {
oldPlayerIdx = 0;
}
Player p = game.getRegisteredPlayers().get(oldPlayerIdx);
if (p.hasLost()) {
// CR 702.26n
Untap.doPhasing(p);
game.getUntap().executeUntil(p);
game.getUpkeep().executeUntil(p);
game.getUpkeep().executeUntilEndOfPhase(p);
game.getEndOfCombat().executeUntilEndOfPhase(p);
game.getEndOfTurn().executeUntil(p);
game.getEndOfTurn().executeUntilEndOfPhase(p);
game.getCleanup().executeUntil(p);
}
}
}
}

View File

@@ -258,7 +258,7 @@ public class Untap extends Phase {
return untap;
}
private static void doPhasing(final Player turn) {
public static void doPhasing(final Player turn) {
// Needs to include phased out cards
final List<Card> list = CardLists.filter(turn.getGame().getCardsIncludePhasingIn(ZoneType.Battlefield),
c -> (c.isPhasedOut(turn) && c.isDirectlyPhasedOut())