Card: changed some logic with facedown and copy states

This commit is contained in:
Hans Mackowiak
2021-04-15 05:59:28 +00:00
parent 7808605a8c
commit 8d42be3bac
9 changed files with 103 additions and 101 deletions

View File

@@ -15,6 +15,7 @@ import com.google.common.collect.Maps;
import forge.GameCommand;
import forge.card.CardStateName;
import forge.card.CardType;
import forge.game.Game;
import forge.game.GameEntity;
import forge.game.GameEntityCounterTable;
@@ -27,6 +28,7 @@ import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.card.CardState;
import forge.game.card.CardUtil;
import forge.game.card.CardView;
import forge.game.card.CardZoneTable;
@@ -651,6 +653,12 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
}
}
// need to be facedown before it hits the battlefield in case of Replacement Effects or Trigger
if (sa.hasParam("FaceDown")) {
gameCard.turnFaceDown(true);
setFaceDownState(gameCard, sa);
}
movedCard = game.getAction().moveTo(gameCard.getController().getZone(destination), gameCard, cause, moveParams);
if (sa.hasParam("Unearth")) {
movedCard.setUnearthed(true);
@@ -662,9 +670,6 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
if (sa.hasParam("LeaveBattlefield")) {
addLeaveBattlefieldReplacement(movedCard, sa, sa.getParam("LeaveBattlefield"));
}
if (sa.hasParam("FaceDown")) {
movedCard.turnFaceDown(true);
}
if (addToCombat(movedCard, movedCard.getController(), sa, "Attacking", "Blocking")) {
combatChanged = true;
}
@@ -1295,34 +1300,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
// need to be facedown before it hits the battlefield in case of Replacement Effects or Trigger
if (sa.hasParam("FaceDown")) {
c.turnFaceDown(true);
// set New Pt doesn't work because this values need to be copyable for clone effects
if (sa.hasParam("FaceDownPower")) {
c.setBasePower(AbilityUtils.calculateAmount(
source, sa.getParam("FaceDownPower"), sa));
}
if (sa.hasParam("FaceDownToughness")) {
c.setBaseToughness(AbilityUtils.calculateAmount(
source, sa.getParam("FaceDownToughness"), sa));
}
if (sa.hasParam("FaceDownAddType")) {
c.addType(Arrays.asList(sa.getParam("FaceDownAddType").split(" & ")));
}
if (sa.hasParam("FaceDownPower") || sa.hasParam("FaceDownToughness")
|| sa.hasParam("FaceDownAddType")) {
final GameCommand unanimate = new GameCommand() {
private static final long serialVersionUID = 8853789549297846163L;
@Override
public void run() {
c.clearStates(CardStateName.FaceDown, true);
}
};
c.addFaceupCommand(unanimate);
}
setFaceDownState(c, sa);
}
movedCard = game.getAction().moveToPlay(c, c.getController(), cause, moveParams);
if (sa.hasParam("Tapped")) {
@@ -1460,6 +1438,39 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
&& sa.getParam("WithTotalCMC") == null;
}
private static void setFaceDownState(Card c, SpellAbility sa) {
final Card source = sa.getHostCard();
CardState faceDown = c.getFaceDownState();
// set New Pt doesn't work because this values need to be copyable for clone effects
if (sa.hasParam("FaceDownPower")) {
faceDown.setBasePower(AbilityUtils.calculateAmount(
source, sa.getParam("FaceDownPower"), sa));
}
if (sa.hasParam("FaceDownToughness")) {
faceDown.setBaseToughness(AbilityUtils.calculateAmount(
source, sa.getParam("FaceDownToughness"), sa));
}
if (sa.hasParam("FaceDownSetType")) {
faceDown.setType(new CardType(Arrays.asList(sa.getParam("FaceDownSetType").split(" & ")), false));
}
if (sa.hasParam("FaceDownPower") || sa.hasParam("FaceDownToughness")
|| sa.hasParam("FaceDownSetType")) {
final GameCommand unanimate = new GameCommand() {
private static final long serialVersionUID = 8853789549297846163L;
@Override
public void run() {
c.clearStates(CardStateName.FaceDown, true);
}
};
c.addFaceupCommand(unanimate);
}
}
/**
* <p>
* removeFromStack.

View File

@@ -5,13 +5,11 @@ import java.util.List;
import com.google.common.collect.Lists;
import forge.card.CardStateName;
import forge.game.Game;
import forge.game.GameObject;
import forge.game.ability.AbilityKey;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.card.CardCloneStates;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardFactory;
@@ -54,6 +52,8 @@ public class MutateEffect extends SpellAbilityEffect {
host.setController(p, 0);
}
final boolean wasFaceDown = target.isFaceDown();
host.setMergedToCard(target);
// If first time mutate, add target first.
if (!target.hasMergedCard()) {
@@ -72,10 +72,15 @@ public class MutateEffect extends SpellAbilityEffect {
// Now add all abilities from bottom cards
final Long ts = game.getNextTimestamp();
target.setMutatedTimestamp(ts);
if (topCard.getCurrentStateName() != CardStateName.FaceDown) {
final CardCloneStates mutatedStates = CardFactory.getMutatedCloneStates(target, sa);
target.addCloneState(mutatedStates, ts);
target.addCloneState(CardFactory.getMutatedCloneStates(target, sa), ts);
// currently used by Tezzeret, Cruel Machinist and Yedora, Grave Gardener
// when mutating onto the FaceDown, their effect should end, then 721.2e would stop trigger
if (wasFaceDown && !target.isFaceDown()) {
target.runFaceupCommands();
}
// Re-register triggers for target card
game.getTriggerHandler().clearActiveTriggers(target, null);
game.getTriggerHandler().registerActiveTrigger(target, false);

View File

@@ -49,7 +49,6 @@ import com.google.common.collect.Table;
import com.google.common.collect.TreeBasedTable;
import forge.GameCommand;
import forge.ImageKeys;
import forge.StaticData;
import forge.card.CardChangedType;
import forge.card.CardDb.SetPreference;
@@ -433,6 +432,9 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
return getState(state, false);
}
public CardState getState(final CardStateName state, boolean skipTextChange) {
if (state == CardStateName.FaceDown) {
return getFaceDownState();
}
if (!skipTextChange) {
CardCloneStates txtStates = getLastTextChangeState();
if (txtStates != null) {
@@ -460,12 +462,19 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
}
public CardState getOriginalState(final CardStateName state) {
if (!states.containsKey(state) && state == CardStateName.FaceDown) {
states.put(CardStateName.FaceDown, CardUtil.getFaceDownCharacteristic(this));
if (state == CardStateName.FaceDown) {
return getFaceDownState();
}
return states.get(state);
}
public CardState getFaceDownState() {
if (!states.containsKey(CardStateName.FaceDown)) {
states.put(CardStateName.FaceDown, CardUtil.getFaceDownCharacteristic(this));
}
return states.get(CardStateName.FaceDown);
}
public void setOriginalStateAsFaceDown() {
// For Ertai's Meddling a morph spell
currentState = CardUtil.getFaceDownCharacteristic(this, CardStateName.Original);
@@ -476,26 +485,25 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
return setState(state, updateView, false);
}
public boolean setState(final CardStateName state, boolean updateView, boolean forceUpdate) {
CardCloneStates textChangeStates = getLastTextChangeState();
// faceDown has higher priority over clone states
// while text change states doesn't apply while the card is faceDown
if (state != CardStateName.FaceDown) {
CardCloneStates textChangeStates = getLastTextChangeState();
if (textChangeStates != null) {
if (!textChangeStates.containsKey(state)) {
throw new RuntimeException(getName() + " tried to switch to non-existant text change state \"" + state + "\"!");
//return false; // Nonexistant state.
}
} else {
CardCloneStates cloneStates = getLastClonedState();
if (cloneStates != null) {
if (!cloneStates.containsKey(state)) {
throw new RuntimeException(getName() + " tried to switch to non-existant cloned state \"" + state + "\"!");
if (textChangeStates != null) {
if (!textChangeStates.containsKey(state)) {
throw new RuntimeException(getName() + " tried to switch to non-existant text change state \"" + state + "\"!");
//return false; // Nonexistant state.
}
} else {
if (!states.containsKey(state)) {
if (state == CardStateName.FaceDown) {
// The face-down state is created lazily only when needed.
states.put(CardStateName.FaceDown, CardUtil.getFaceDownCharacteristic(this));
} else {
CardCloneStates cloneStates = getLastClonedState();
if (cloneStates != null) {
if (!cloneStates.containsKey(state)) {
throw new RuntimeException(getName() + " tried to switch to non-existant cloned state \"" + state + "\"!");
//return false; // Nonexistant state.
}
} else {
if (!states.containsKey(state)) {
System.out.println(getName() + " tried to switch to non-existant state \"" + state + "\"!");
return false; // Nonexistant state.
}
@@ -554,20 +562,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
states.putAll(map);
}
// was only used for Clone Effects
@Deprecated
public void switchStates(final CardStateName from, final CardStateName to, boolean updateView) {
final CardState tmp = states.get(from);
states.put(from, states.get(to));
states.put(to, tmp);
if (currentStateName == from) {
setState(to, false);
}
if (updateView) {
view.updateState(this);
}
}
public final void addAlternateState(final CardStateName state, final boolean updateView) {
states.put(state, new CardState(this, state));
if (updateView) {
@@ -619,9 +613,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
return false;
}
if (hasMergedCard()) {
removeMutatedStates();
}
CardCollectionView cards = hasMergedCard() ? getMergedCards() : new CardCollection(this);
boolean retResult = false;
for (final Card c : cards) {
@@ -633,7 +624,8 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
boolean result = c.changeToState(c.backside ? CardStateName.Transformed : CardStateName.Original);
retResult = retResult || result;
}
if (hasMergedCard()) {
if (retResult && hasMergedCard()) {
removeMutatedStates();
rebuildMutatedStates(cause);
}
@@ -654,9 +646,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
return false;
}
if (hasMergedCard()) {
removeMutatedStates();
}
CardCollectionView cards = hasMergedCard() ? getMergedCards() : new CardCollection(this);
boolean retResult = false;
for (final Card c : cards) {
@@ -668,6 +657,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
retResult = retResult || result;
}
if (retResult && hasMergedCard()) {
removeMutatedStates();
rebuildMutatedStates(cause);
game.getTriggerHandler().clearActiveTriggers(this, null);
game.getTriggerHandler().registerActiveTrigger(this, false);
@@ -716,9 +706,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
}
public boolean turnFaceDown(boolean override) {
if (hasMergedCard()) {
removeMutatedStates();
}
CardCollectionView cards = hasMergedCard() ? getMergedCards() : new CardCollection(this);
boolean retResult = false;
for (final Card c : cards) {
@@ -730,7 +717,8 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
}
}
}
if (hasMergedCard()) {
if (retResult && hasMergedCard()) {
removeMutatedStates();
rebuildMutatedStates(null);
game.getTriggerHandler().clearActiveTriggers(this, null);
game.getTriggerHandler().registerActiveTrigger(this, false);
@@ -756,9 +744,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
return false;
}
if (hasMergedCard()) {
removeMutatedStates();
}
CardCollectionView cards = hasMergedCard() ? getMergedCards() : new CardCollection(this);
boolean retResult = false;
for (final Card c : cards) {
@@ -778,7 +763,8 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
}
retResult = retResult || result;
}
if (hasMergedCard()) {
if (retResult && hasMergedCard()) {
removeMutatedStates();
rebuildMutatedStates(cause);
game.getTriggerHandler().clearActiveTriggers(this, null);
game.getTriggerHandler().registerActiveTrigger(this, false);
@@ -1170,7 +1156,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
}
}
public final void rebuildMutatedStates(final CardTraitBase sa) {
if (getCurrentStateName() != CardStateName.FaceDown) {
if (!isFaceDown()) {
final CardCloneStates mutatedStates = CardFactory.getMutatedCloneStates(this, sa);
addCloneState(mutatedStates, getMutatedTimestamp());
}
@@ -5596,10 +5582,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
}
public final void setManifested(final boolean manifested) {
this.manifested = manifested;
final String image = manifested ? ImageKeys.MANIFEST_IMAGE : ImageKeys.MORPH_IMAGE;
// Note: This should only be called after state has been set to CardStateName.FaceDown,
// so the below call should be valid since the state should have been created already.
getState(CardStateName.FaceDown).setImageKey(ImageKeys.getTokenKey(image));
}
public final boolean isForetold() {
@@ -5616,7 +5598,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
public final void setForetold(final boolean foretold) {
this.foretold = foretold;
getState(CardStateName.FaceDown).setImageKey(ImageKeys.getTokenKey(ImageKeys.FORETELL_IMAGE));
}
public boolean isForetoldByEffect() {

View File

@@ -623,7 +623,7 @@ public class CardFactory {
// if something is cloning a facedown card, it only clones the
// facedown state into original
final CardState ret = new CardState(out, CardStateName.Original);
ret.copyFrom(in.getState(CardStateName.FaceDown, true), false);
ret.copyFrom(in.getFaceDownState(), false);
result.put(CardStateName.Original, ret);
} else if (in.isFlipCard()) {
// if something is cloning a flip card, copy both original and
@@ -828,9 +828,6 @@ public class CardFactory {
state.removeIntrinsicKeyword("Devoid");
}
}
// Dont copy the facedown state, make new one
result.put(CardStateName.FaceDown, CardUtil.getFaceDownCharacteristic(out));
return result;
}

View File

@@ -380,10 +380,7 @@ public final class CardUtil {
//show hidden if exiled facedown
if (state == CardStateName.FaceDown) {
if (c.isInZone(ZoneType.Exile))
ret.setImageKey(ImageKeys.getTokenKey(c.isForetold() ? ImageKeys.FORETELL_IMAGE : ImageKeys.HIDDEN_CARD));
else
ret.setImageKey(ImageKeys.getTokenKey(c.isManifested() ? ImageKeys.MANIFEST_IMAGE : ImageKeys.MORPH_IMAGE));
ret.setImageKey(ImageKeys.getTokenKey(ImageKeys.HIDDEN_CARD));
} else {
ret.setImageKey(c.getImageKey());
}

View File

@@ -142,6 +142,10 @@ public class CardView extends GameEntityView {
return get(TrackableProperty.Foretold);
}
public boolean isManifested() {
return get(TrackableProperty.Manifested);
}
public boolean isFlipCard() {
return get(TrackableProperty.FlipCard);
}
@@ -792,6 +796,7 @@ public class CardView extends GameEntityView {
set(TrackableProperty.FlipCard, c.isFlipCard());
set(TrackableProperty.Facedown, c.isFaceDown());
set(TrackableProperty.Foretold, c.isForetold());
set(TrackableProperty.Manifested, c.isManifested());
set(TrackableProperty.Adventure, c.isAdventureCard());
set(TrackableProperty.DoubleFaced, c.isDoubleFaced());
set(TrackableProperty.Modal, c.isModal());
@@ -1059,6 +1064,12 @@ public class CardView extends GameEntityView {
return getImageKey(null);
}
public String getImageKey(Iterable<PlayerView> viewers) {
if (getState() == CardStateName.FaceDown) {
if (getCard().getZone() == ZoneType.Exile) {
return ImageKeys.getTokenKey(getCard().isForeTold() ? ImageKeys.FORETELL_IMAGE : ImageKeys.HIDDEN_CARD);
}
return ImageKeys.getTokenKey(getCard().isManifested() ? ImageKeys.MANIFEST_IMAGE : ImageKeys.MORPH_IMAGE);
}
if (canBeShownToAny(viewers)) {
return get(TrackableProperty.ImageKey);
}

View File

@@ -27,6 +27,7 @@ public enum TrackableProperty {
Flipped(TrackableTypes.BooleanType),
Facedown(TrackableTypes.BooleanType),
Foretold(TrackableTypes.BooleanType),
Manifested(TrackableTypes.BooleanType),
Modal(TrackableTypes.BooleanType),
Adventure(TrackableTypes.BooleanType),
DoubleFaced(TrackableTypes.BooleanType),