Merge pull request #2449 from Card-Forge/flashbackCastKeyword

Flashback: use castKeyword
This commit is contained in:
Anthony Calosa
2023-02-13 08:37:38 +08:00
committed by GitHub
8 changed files with 53 additions and 84 deletions

View File

@@ -265,31 +265,21 @@ public class GameAction {
copied.setTimestamp(c.getTimestamp());
if (zoneTo.is(ZoneType.Stack)) {
// when moving to stack, copy changed card information
copied.setChangedCardColors(c.getChangedCardColorsTable());
copied.setChangedCardColorsCharacterDefining(c.getChangedCardColorsCharacterDefiningTable());
copied.setChangedCardKeywords(c.getChangedCardKeywords());
copied.setChangedCardTypes(c.getChangedCardTypesTable());
copied.setChangedCardTypesCharacterDefining(c.getChangedCardTypesCharacterDefiningTable());
copied.setChangedCardNames(c.getChangedCardNames());
copied.setChangedCardTraits(c.getChangedCardTraits());
copied.setDrawnThisTurn(c.getDrawnThisTurn());
copied.copyChangedTextFrom(c);
// clean up changes that come from its own static abilities
copied.cleanupCopiedChangesFrom(c);
// try not to copy changed stats when moving to stack
// copy exiled properties when adding to stack
// will be cleanup later in MagicStack
copied.setExiledWith(c.getExiledWith());
copied.setExiledBy(c.getExiledBy());
copied.setDrawnThisTurn(c.getDrawnThisTurn());
// copy bestow timestamp
copied.setBestowTimestamp(c.getBestowTimestamp());
if (cause != null && cause.isSpell() && c.equals(cause.getHostCard())) {
copied.setCastSA(cause);
copied.setSplitStateToPlayAbility(cause);
// CR 112.2 A spells controller is, by default, the player who put it on the stack.
copied.setController(cause.getActivatingPlayer(), 0);
KeywordInterface kw = cause.getKeyword();
if (kw != null) {
copied.addKeywordForStaticAbility(kw);
@@ -619,10 +609,12 @@ public class GameAction {
checkStaticAbilities();
// 400.7g try adding keyword back into card if it doesn't already have it
if (zoneTo.is(ZoneType.Stack) && cause != null && cause.isSpell() && c.equals(cause.getHostCard())) {
if (zoneTo.is(ZoneType.Stack) && cause != null && cause.isSpell() && !cause.isIntrinsic() && c.equals(cause.getHostCard())) {
if (cause.getKeyword() != null) {
if (!copied.getKeywords().contains(cause.getKeyword())) {
copied.addChangedCardKeywordsInternal(ImmutableList.of(cause.getKeyword()), null, false, game.getTimestamp(), 0, false);
copied.addChangedCardKeywordsInternal(ImmutableList.of(cause.getKeyword()), null, false, game.getNextTimestamp(), 0, false);
// update Keyword Cache
copied.updateKeywords();
}
}
}
@@ -874,17 +866,7 @@ public class GameAction {
return moveToStack(c, cause, params);
}
public final Card moveToStack(final Card c, SpellAbility cause, Map<AbilityKey, Object> params) {
Card result = moveTo(game.getStackZone(), c, cause, params);
if (cause != null && cause.isSpell() && result.equals(cause.getHostCard())) {
result.setSplitStateToPlayAbility(cause);
// CR 112.2 A spells controller is, by default, the player who put it on the stack.
result.setController(cause.getActivatingPlayer(), 0);
// for triggers like from Wild-Magic Sorcerer
game.getAction().checkStaticAbilities(false);
game.getTriggerHandler().resetActiveTriggers();
}
return result;
return moveTo(game.getStackZone(), c, cause, params);
}
public final Card moveToGraveyard(final Card c, SpellAbility cause) {

View File

@@ -224,6 +224,7 @@ public final class GameActionUtil {
newSA.setAlternativeCost(AlternativeCost.Escape);
newSA.getRestrictions().setZone(ZoneType.Graveyard);
newSA.setIntrinsic(inst.isIntrinsic());
alternatives.add(newSA);
} else if (keyword.startsWith("Flashback")) {
@@ -255,6 +256,7 @@ public final class GameActionUtil {
flashback.setAlternativeCost(AlternativeCost.Flashback);
flashback.getRestrictions().setZone(ZoneType.Graveyard);
flashback.setKeyword(inst);
flashback.setIntrinsic(inst.isIntrinsic());
alternatives.add(flashback);
} else if (keyword.startsWith("Foretell")) {
// Foretell cast only from Exile

View File

@@ -20,6 +20,7 @@ package forge.game.card;
import com.esotericsoftware.minlog.Log;
import com.google.common.base.Predicates;
import com.google.common.collect.*;
import forge.GameCommand;
import forge.StaticData;
import forge.card.*;
@@ -4587,6 +4588,17 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
}
}
public Table<Long, String, KeywordInterface> getStoredKeywords() {
return storedKeywords;
}
public void setStoredKeywords(Table<Long, String, KeywordInterface> table, boolean lki) {
storedKeywords.clear();
for (Table.Cell<Long, String, KeywordInterface> c : table.cellSet()) {
storedKeywords.put(c.getRowKey(), c.getColumnKey(), c.getValue().copy(this, lki));
}
}
public final void addChangedCardKeywordsByText(final List<KeywordInterface> keywords, final long timestamp, final long staticId, final boolean updateView) {
// keywords should already created for Card, so no addKeywordsToCard
// this one is done for Volrath's Shapeshifter which replaces all the card text

View File

@@ -2285,7 +2285,7 @@ public class CardFactoryUtil {
} else if (keyword.startsWith("Flashback")) {
StringBuilder sb = new StringBuilder();
sb.append("Event$ Moved | ValidCard$ Card.Self | Origin$ Stack | ExcludeDestination$ Exile ");
sb.append("| ValidStackSa$ Spell.Flashback | Description$ Flashback");
sb.append("| ValidStackSa$ Spell.Flashback+castKeyword | Description$ Flashback");
if (keyword.contains(":")) { // K:Flashback:Cost:ExtraParams:ExtraDescription
final String[] k = keyword.split(":");

View File

@@ -293,6 +293,8 @@ public final class CardUtil {
newCopy.setChangedCardNames(in.getChangedCardNames());
newCopy.setChangedCardTraits(in.getChangedCardTraits());
newCopy.setStoredKeywords(in.getStoredKeywords(), true);
newCopy.copyChangedTextFrom(in);
newCopy.setTimestamp(in.getTimestamp());

View File

@@ -20,6 +20,9 @@ import io.sentry.Breadcrumb;
import io.sentry.Sentry;
public abstract class KeywordInstance<T extends KeywordInstance<?>> implements KeywordInterface {
private Card hostCard = null;
private boolean intrinsic = false;
private Keyword keyword;
private String original;
private long staticId = 0;
@@ -87,6 +90,8 @@ public abstract class KeywordInstance<T extends KeywordInstance<?>> implements K
* @see forge.game.keyword.KeywordInterface#createTraits(forge.game.card.Card, boolean, boolean)
*/
public final void createTraits(final Card host, final boolean intrinsic, final boolean clear) {
this.hostCard = host;
this.intrinsic = intrinsic;
if (clear) {
triggers.clear();
replacements.clear();
@@ -249,7 +254,7 @@ public abstract class KeywordInstance<T extends KeywordInstance<?>> implements K
public KeywordInterface copy(final Card host, final boolean lki) {
try {
KeywordInstance<?> result = (KeywordInstance<?>) super.clone();
result.hostCard = host;
result.abilities = Lists.newArrayList();
for (SpellAbility sa : this.abilities) {
SpellAbility copy = sa.copy(host, lki);
@@ -300,11 +305,17 @@ public abstract class KeywordInstance<T extends KeywordInstance<?>> implements K
return !list.isEmpty() && keyword.isMultipleRedundant;
}
@Override
public Card getHostCard() {
return hostCard;
}
/* (non-Javadoc)
* @see forge.game.keyword.KeywordInterface#setHostCard(forge.game.card.Card)
*/
@Override
public void setHostCard(Card host) {
this.hostCard = host;
for (SpellAbility sa : this.abilities) {
sa.setHostCard(host);
}
@@ -322,8 +333,14 @@ public abstract class KeywordInstance<T extends KeywordInstance<?>> implements K
}
}
@Override
public boolean isIntrinsic() {
return intrinsic;
}
@Override
public void setIntrinsic(final boolean value) {
this.intrinsic = value;
for (SpellAbility sa : this.abilities) {
sa.setIntrinsic(value);
}

View File

@@ -11,6 +11,11 @@ import forge.game.trigger.Trigger;
public interface KeywordInterface extends Cloneable {
Card getHostCard();
void setHostCard(final Card host);
boolean isIntrinsic();
void setIntrinsic(final boolean value);
String getOriginal();
Keyword getKeyword();
@@ -34,8 +39,6 @@ public interface KeywordInterface extends Cloneable {
void addSpellAbility(final SpellAbility s);
void addStaticAbility(final StaticAbility st);
void setHostCard(final Card host);
void setIntrinsic(final boolean value);
/**
* @return the triggers

View File

@@ -31,7 +31,6 @@ import org.apache.commons.lang3.StringUtils;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.collect.Table;
import forge.game.CardTraitBase;
import forge.game.Game;
@@ -48,10 +47,7 @@ import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardDamageMap;
import forge.game.card.CardState;
import forge.game.card.CardTraitChanges;
import forge.game.card.CardUtil;
import forge.game.keyword.KeywordInterface;
import forge.game.keyword.KeywordsChange;
import forge.game.player.Player;
import forge.game.player.PlayerCollection;
import forge.game.spellability.AbilitySub;
@@ -110,53 +106,6 @@ public class ReplacementHandler {
game.getAction().checkStaticAbilities(false, Sets.newHashSet(affectedLKI), preList);
checkAgain = true;
// need to check if Intrinsic has run
for (ReplacementEffect re : affectedLKI.getReplacementEffects()) {
if (re.isIntrinsic() && this.hasRun.contains(re)) {
re.setHasRun(true);
}
}
// need to check non Intrinsic
for (Table.Cell<Long, Long, CardTraitChanges> e : affectedLKI.getChangedCardTraits().cellSet()) {
boolean hasRunRE = false;
String skey = String.valueOf(e.getRowKey()) + ":" + String.valueOf(e.getColumnKey());
for (ReplacementEffect re : this.hasRun) {
if (!re.isIntrinsic() && skey.equals(re.getSVar("_ReplacedTimestamp"))) {
hasRunRE = true;
break;
}
}
for (ReplacementEffect re : e.getValue().getReplacements()) {
re.setSVar("_ReplacedTimestamp", skey);
if (hasRunRE) {
re.setHasRun(true);
}
}
}
for (Table.Cell<Long, Long, KeywordsChange> e : affectedLKI.getChangedCardKeywords().cellSet()) {
boolean hasRunRE = false;
String skey = String.valueOf(e.getRowKey()) + ":" + String.valueOf(e.getColumnKey());
for (ReplacementEffect re : this.hasRun) {
if (!re.isIntrinsic() && skey.equals(re.getSVar("_ReplacedTimestamp"))) {
hasRunRE = true;
break;
}
}
for (KeywordInterface k : e.getValue().getKeywords()) {
for (ReplacementEffect re : k.getReplacements()) {
re.setSVar("_ReplacedTimestamp", skey);
if (hasRunRE) {
re.setHasRun(true);
}
}
}
}
runParams.put(AbilityKey.Affected, affectedLKI);
}
@@ -222,6 +171,8 @@ public class ReplacementHandler {
for (final ReplacementEffect re : affectedLKI.getReplacementEffects()) {
re.setHostCard(affectedCard);
}
// need to copy stored keywords from lki into real object to prevent the replacement effect from making new ones
affectedCard.setStoredKeywords(affectedLKI.getStoredKeywords(), true);
runParams.put(AbilityKey.Affected, affectedCard);
runParams.put(AbilityKey.NewCard, CardUtil.getLKICopy(affectedLKI));
}