Merge branch 'phasing' into 'master'

Phasing Fixes

Closes #531

See merge request core-developers/forge!4808
This commit is contained in:
Michael Kamensky
2021-06-05 14:27:11 +00:00
12 changed files with 31 additions and 20 deletions

View File

@@ -676,7 +676,6 @@ public class AiAttackController {
* @return a {@link forge.game.combat.Combat} object.
*/
public final void declareAttackers(final Combat combat) {
if (this.attackers.isEmpty()) {
return;
}

View File

@@ -6,7 +6,6 @@ package forge.card;
*/
public class CardAiHints {
private final boolean isRemovedFromAIDecks;
private final boolean isRemovedFromRandomDecks;
private final boolean isRemovedFromNonCommanderDecks;
@@ -15,7 +14,6 @@ public class CardAiHints {
private final DeckHints deckNeeds;
private final DeckHints deckHas;
public CardAiHints(boolean remAi, boolean remRandom, boolean remUnlessCommander, DeckHints dh, DeckHints dn, DeckHints has) {
isRemovedFromAIDecks = remAi;
isRemovedFromRandomDecks = remRandom;
@@ -90,5 +88,4 @@ public class CardAiHints {
}
}
}

View File

@@ -756,7 +756,7 @@ public class Game {
// Rule 800.4 Losing a Multiplayer game
CardCollectionView cards = this.getCardsInGame();
boolean planarControllerLost = false;
boolean isMultiplayer = this.getPlayers().size() > 2;
boolean isMultiplayer = getPlayers().size() > 2;
// 702.142f & 707.9
// If a player leaves the game, all face-down cards that player owns must be revealed to all players.
@@ -793,16 +793,16 @@ public class Game {
}
getAction().ceaseToExist(c, false);
// CR 603.2f owner of trigger source lost game
triggerHandler.clearDelayedTrigger(c);
getTriggerHandler().clearDelayedTrigger(c);
} else {
// return stolen permanents
if (c.getController().equals(p) && c.isInZone(ZoneType.Battlefield)) {
if ((c.getController().equals(p) || c.getZone().getPlayer().equals(p)) && c.isInZone(ZoneType.Battlefield)) {
c.removeTempController(p);
getAction().controllerChangeZoneCorrection(c);
}
c.removeTempController(p);
if (c.getController().equals(p)) {
this.getAction().exile(c, null);
getAction().exile(c, null);
}
}
} else {
@@ -830,7 +830,7 @@ public class Game {
}
// Remove leftover items from
this.getStack().removeInstancesControlledBy(p);
getStack().removeInstancesControlledBy(p);
getTriggerHandler().onPlayerLost(p);

View File

@@ -21,6 +21,7 @@ import forge.game.GameObject;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardUtil;
import forge.game.card.CardZoneTable;
import forge.game.combat.Combat;
import forge.game.phase.PhaseType;
@@ -296,7 +297,7 @@ public abstract class SpellAbilityEffect {
}
delTrig.append("| TriggerDescription$ ").append(desc);
final Trigger trig = TriggerHandler.parseTrigger(delTrig.toString(), sa.getHostCard(), intrinsic);
final Trigger trig = TriggerHandler.parseTrigger(delTrig.toString(), CardUtil.getLKICopy(sa.getHostCard()), intrinsic);
for (final Card c : crds) {
trig.addRemembered(c);

View File

@@ -502,7 +502,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
// gameCard is LKI in that case, the card is not in game anymore
// or the timestamp did change
// this should check Self too
if (gameCard == null || !tgtC.equalsWithTimestamp(gameCard)) {
if (gameCard == null || !tgtC.equalsWithTimestamp(gameCard) || gameCard.isPhasedOut()) {
continue;
}
if (sa.usesTargeting() && !gameCard.canBeTargetedBy(sa)) {

View File

@@ -72,7 +72,7 @@ public class PhasesEffect extends SpellAbilityEffect {
for (final Card tgtC : tgtCards) {
if (!tgtC.isPhasedOut()) {
tgtC.phase(false);
if ( tgtC.isPhasedOut()) {
if (tgtC.isPhasedOut()) {
tgtC.setWontPhaseInNormal(wontPhaseInNormal);
}
}

View File

@@ -4609,8 +4609,9 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
setDirectlyPhasedOut(direct);
}
if (hasCardAttachments()) {
for (final Card eq : getAttachedCards()) {
// CR 702.25g
if (!getAllAttachedCards().isEmpty()) {
for (final Card eq : getAllAttachedCards()) {
if (eq.isPhasedOut() == phasingIn) {
eq.phase(fromUntapStep, false);
}
@@ -4627,7 +4628,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
}
private boolean switchPhaseState(final boolean fromUntapStep) {
if (phasedOut && hasKeyword("CARDNAME can't phase in.")) {
return false;
}
@@ -4646,6 +4646,9 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
// If this is currently PhasedIn, it's about to phase out.
// Run trigger before it does because triggers don't work with phased out objects
getGame().getTriggerHandler().runTrigger(TriggerType.PhaseOut, runParams, false);
// when it doesn't exist the game will no longer see it as tapped
runUntapCommands();
// TODO need to run UntilHostLeavesPlay commands but only when worded "for as long as"
}
setPhasedOut(!phasedOut);
@@ -4660,6 +4663,8 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
getGame().getTriggerHandler().runTrigger(TriggerType.PhaseIn, runParams, false);
}
game.updateLastStateForCard(this);
return true;
}
@@ -5769,11 +5774,11 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
}
public final boolean canBeDestroyed() {
return isInPlay() && (!hasKeyword(Keyword.INDESTRUCTIBLE) || (isCreature() && getNetToughness() <= 0));
return isInPlay() && !isPhasedOut() && (!hasKeyword(Keyword.INDESTRUCTIBLE) || (isCreature() && getNetToughness() <= 0));
}
public final boolean canBeSacrificed() {
return isInPlay() && !this.isPhasedOut() && !hasKeyword("CARDNAME can't be sacrificed.");
return isInPlay() && !isPhasedOut() && !hasKeyword("CARDNAME can't be sacrificed.");
}
@Override

View File

@@ -47,6 +47,11 @@ public class CardProperty {
final Card lki = game.getChangeZoneLKIInfo(card);
final Player controller = lki.getController();
// CR 702.25b if card is phased out it will not count unless specifically asked for
if (card.isPhasedOut() && !property.contains("phasedOut")) {
return false;
}
// by name can also have color names, so needs to happen before colors.
if (property.startsWith("named")) {
String name = TextUtil.fastReplace(property.substring(5), ";", ","); // for some legendary cards

View File

@@ -253,6 +253,8 @@ public final class CardUtil {
newCopy.setCounters(Maps.newHashMap(in.getCounters()));
newCopy.setColor(in.determineColor().getColor());
newCopy.setPhasedOut(in.isPhasedOut());
newCopy.setReceivedDamageFromThisTurn(in.getReceivedDamageFromThisTurn());
newCopy.setDamageHistory(in.getDamageHistory());
for (Card c : in.getBlockedThisTurn()) {

View File

@@ -475,7 +475,7 @@ public class PhaseHandler implements java.io.Serializable {
boolean manaBurns = game.getRules().hasManaBurn() ||
(game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.manaBurn));
if (manaBurns) {
p.loseLife(burn,true);
p.loseLife(burn, true);
}
}
@@ -485,7 +485,7 @@ public class PhaseHandler implements java.io.Serializable {
break;
case UPKEEP:
for (Card c : game.getCardsIn(ZoneType.Battlefield)) {
for (Card c : game.getCardsIncludePhasingIn(ZoneType.Battlefield)) {
c.getDamageHistory().setNotAttackedSinceLastUpkeepOf(playerTurn);
c.getDamageHistory().setNotBlockedSinceLastUpkeepOf(playerTurn);
c.getDamageHistory().setNotBeenBlockedSinceLastUpkeepOf(playerTurn);

View File

@@ -72,6 +72,8 @@ public class Untap extends Phase {
final Player turn = game.getPhaseHandler().getPlayerTurn();
Untap.doPhasing(turn);
game.getAction().checkStaticAbilities();
doUntap();
}

View File

@@ -8,5 +8,5 @@ SVar:WinGame:DB$ WinsGame | Defined$ You | ConditionCheckSVar$ TalkedEnough | Co
SVar:TalkedEnough:Count$CardCounters.FILIBUSTER
T:Mode$ DamageDone | ValidTarget$ You | TriggerZones$ Battlefield | Execute$ RemoveCounter | TriggerDescription$ Whenever a source deals damage to you, remove a filibuster counter from CARDNAME.
SVar:RemoveCounter:DB$ RemoveCounter | Defined$ Self | CounterType$ FILIBUSTER | CounterNum$ 1
SVar:Picture:http://www.wizards.com/global/images/magic/general/azors_elocutors.jpg
SVar:AIEvaluationModifier:30
Oracle:At the beginning of your upkeep, put a filibuster counter on Azor's Elocutors. Then if Azor's Elocutors has five or more filibuster counters on it, you win the game.\nWhenever a source deals damage to you, remove a filibuster counter from Azor's Elocutors.