mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-14 17:58:01 +00:00
Merge pull request #2449 from Card-Forge/flashbackCastKeyword
Flashback: use castKeyword
This commit is contained in:
@@ -265,31 +265,21 @@ public class GameAction {
|
|||||||
copied.setTimestamp(c.getTimestamp());
|
copied.setTimestamp(c.getTimestamp());
|
||||||
|
|
||||||
if (zoneTo.is(ZoneType.Stack)) {
|
if (zoneTo.is(ZoneType.Stack)) {
|
||||||
// when moving to stack, copy changed card information
|
// try not to copy changed stats when moving to stack
|
||||||
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);
|
|
||||||
|
|
||||||
// copy exiled properties when adding to stack
|
// copy exiled properties when adding to stack
|
||||||
// will be cleanup later in MagicStack
|
// will be cleanup later in MagicStack
|
||||||
copied.setExiledWith(c.getExiledWith());
|
copied.setExiledWith(c.getExiledWith());
|
||||||
copied.setExiledBy(c.getExiledBy());
|
copied.setExiledBy(c.getExiledBy());
|
||||||
|
copied.setDrawnThisTurn(c.getDrawnThisTurn());
|
||||||
|
|
||||||
// copy bestow timestamp
|
|
||||||
copied.setBestowTimestamp(c.getBestowTimestamp());
|
|
||||||
|
|
||||||
if (cause != null && cause.isSpell() && c.equals(cause.getHostCard())) {
|
if (cause != null && cause.isSpell() && c.equals(cause.getHostCard())) {
|
||||||
copied.setCastSA(cause);
|
copied.setCastSA(cause);
|
||||||
|
copied.setSplitStateToPlayAbility(cause);
|
||||||
|
|
||||||
|
// CR 112.2 A spell’s controller is, by default, the player who put it on the stack.
|
||||||
|
copied.setController(cause.getActivatingPlayer(), 0);
|
||||||
KeywordInterface kw = cause.getKeyword();
|
KeywordInterface kw = cause.getKeyword();
|
||||||
if (kw != null) {
|
if (kw != null) {
|
||||||
copied.addKeywordForStaticAbility(kw);
|
copied.addKeywordForStaticAbility(kw);
|
||||||
@@ -619,10 +609,12 @@ public class GameAction {
|
|||||||
checkStaticAbilities();
|
checkStaticAbilities();
|
||||||
|
|
||||||
// 400.7g try adding keyword back into card if it doesn't already have it
|
// 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 (cause.getKeyword() != null) {
|
||||||
if (!copied.getKeywords().contains(cause.getKeyword())) {
|
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);
|
return moveToStack(c, cause, params);
|
||||||
}
|
}
|
||||||
public final Card moveToStack(final Card c, SpellAbility cause, Map<AbilityKey, Object> params) {
|
public final Card moveToStack(final Card c, SpellAbility cause, Map<AbilityKey, Object> params) {
|
||||||
Card result = moveTo(game.getStackZone(), c, cause, params);
|
return moveTo(game.getStackZone(), c, cause, params);
|
||||||
if (cause != null && cause.isSpell() && result.equals(cause.getHostCard())) {
|
|
||||||
result.setSplitStateToPlayAbility(cause);
|
|
||||||
|
|
||||||
// CR 112.2 A spell’s 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public final Card moveToGraveyard(final Card c, SpellAbility cause) {
|
public final Card moveToGraveyard(final Card c, SpellAbility cause) {
|
||||||
|
|||||||
@@ -224,6 +224,7 @@ public final class GameActionUtil {
|
|||||||
|
|
||||||
newSA.setAlternativeCost(AlternativeCost.Escape);
|
newSA.setAlternativeCost(AlternativeCost.Escape);
|
||||||
newSA.getRestrictions().setZone(ZoneType.Graveyard);
|
newSA.getRestrictions().setZone(ZoneType.Graveyard);
|
||||||
|
newSA.setIntrinsic(inst.isIntrinsic());
|
||||||
|
|
||||||
alternatives.add(newSA);
|
alternatives.add(newSA);
|
||||||
} else if (keyword.startsWith("Flashback")) {
|
} else if (keyword.startsWith("Flashback")) {
|
||||||
@@ -255,6 +256,7 @@ public final class GameActionUtil {
|
|||||||
flashback.setAlternativeCost(AlternativeCost.Flashback);
|
flashback.setAlternativeCost(AlternativeCost.Flashback);
|
||||||
flashback.getRestrictions().setZone(ZoneType.Graveyard);
|
flashback.getRestrictions().setZone(ZoneType.Graveyard);
|
||||||
flashback.setKeyword(inst);
|
flashback.setKeyword(inst);
|
||||||
|
flashback.setIntrinsic(inst.isIntrinsic());
|
||||||
alternatives.add(flashback);
|
alternatives.add(flashback);
|
||||||
} else if (keyword.startsWith("Foretell")) {
|
} else if (keyword.startsWith("Foretell")) {
|
||||||
// Foretell cast only from Exile
|
// Foretell cast only from Exile
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ package forge.game.card;
|
|||||||
import com.esotericsoftware.minlog.Log;
|
import com.esotericsoftware.minlog.Log;
|
||||||
import com.google.common.base.Predicates;
|
import com.google.common.base.Predicates;
|
||||||
import com.google.common.collect.*;
|
import com.google.common.collect.*;
|
||||||
|
|
||||||
import forge.GameCommand;
|
import forge.GameCommand;
|
||||||
import forge.StaticData;
|
import forge.StaticData;
|
||||||
import forge.card.*;
|
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) {
|
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
|
// keywords should already created for Card, so no addKeywordsToCard
|
||||||
// this one is done for Volrath's Shapeshifter which replaces all the card text
|
// this one is done for Volrath's Shapeshifter which replaces all the card text
|
||||||
|
|||||||
@@ -2285,7 +2285,7 @@ public class CardFactoryUtil {
|
|||||||
} else if (keyword.startsWith("Flashback")) {
|
} else if (keyword.startsWith("Flashback")) {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
sb.append("Event$ Moved | ValidCard$ Card.Self | Origin$ Stack | ExcludeDestination$ Exile ");
|
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
|
if (keyword.contains(":")) { // K:Flashback:Cost:ExtraParams:ExtraDescription
|
||||||
final String[] k = keyword.split(":");
|
final String[] k = keyword.split(":");
|
||||||
|
|||||||
@@ -293,6 +293,8 @@ public final class CardUtil {
|
|||||||
newCopy.setChangedCardNames(in.getChangedCardNames());
|
newCopy.setChangedCardNames(in.getChangedCardNames());
|
||||||
newCopy.setChangedCardTraits(in.getChangedCardTraits());
|
newCopy.setChangedCardTraits(in.getChangedCardTraits());
|
||||||
|
|
||||||
|
newCopy.setStoredKeywords(in.getStoredKeywords(), true);
|
||||||
|
|
||||||
newCopy.copyChangedTextFrom(in);
|
newCopy.copyChangedTextFrom(in);
|
||||||
|
|
||||||
newCopy.setTimestamp(in.getTimestamp());
|
newCopy.setTimestamp(in.getTimestamp());
|
||||||
|
|||||||
@@ -20,6 +20,9 @@ import io.sentry.Breadcrumb;
|
|||||||
import io.sentry.Sentry;
|
import io.sentry.Sentry;
|
||||||
|
|
||||||
public abstract class KeywordInstance<T extends KeywordInstance<?>> implements KeywordInterface {
|
public abstract class KeywordInstance<T extends KeywordInstance<?>> implements KeywordInterface {
|
||||||
|
private Card hostCard = null;
|
||||||
|
private boolean intrinsic = false;
|
||||||
|
|
||||||
private Keyword keyword;
|
private Keyword keyword;
|
||||||
private String original;
|
private String original;
|
||||||
private long staticId = 0;
|
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)
|
* @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) {
|
public final void createTraits(final Card host, final boolean intrinsic, final boolean clear) {
|
||||||
|
this.hostCard = host;
|
||||||
|
this.intrinsic = intrinsic;
|
||||||
if (clear) {
|
if (clear) {
|
||||||
triggers.clear();
|
triggers.clear();
|
||||||
replacements.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) {
|
public KeywordInterface copy(final Card host, final boolean lki) {
|
||||||
try {
|
try {
|
||||||
KeywordInstance<?> result = (KeywordInstance<?>) super.clone();
|
KeywordInstance<?> result = (KeywordInstance<?>) super.clone();
|
||||||
|
result.hostCard = host;
|
||||||
result.abilities = Lists.newArrayList();
|
result.abilities = Lists.newArrayList();
|
||||||
for (SpellAbility sa : this.abilities) {
|
for (SpellAbility sa : this.abilities) {
|
||||||
SpellAbility copy = sa.copy(host, lki);
|
SpellAbility copy = sa.copy(host, lki);
|
||||||
@@ -300,11 +305,17 @@ public abstract class KeywordInstance<T extends KeywordInstance<?>> implements K
|
|||||||
return !list.isEmpty() && keyword.isMultipleRedundant;
|
return !list.isEmpty() && keyword.isMultipleRedundant;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Card getHostCard() {
|
||||||
|
return hostCard;
|
||||||
|
}
|
||||||
|
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
* @see forge.game.keyword.KeywordInterface#setHostCard(forge.game.card.Card)
|
* @see forge.game.keyword.KeywordInterface#setHostCard(forge.game.card.Card)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void setHostCard(Card host) {
|
public void setHostCard(Card host) {
|
||||||
|
this.hostCard = host;
|
||||||
for (SpellAbility sa : this.abilities) {
|
for (SpellAbility sa : this.abilities) {
|
||||||
sa.setHostCard(host);
|
sa.setHostCard(host);
|
||||||
}
|
}
|
||||||
@@ -322,8 +333,14 @@ public abstract class KeywordInstance<T extends KeywordInstance<?>> implements K
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isIntrinsic() {
|
||||||
|
return intrinsic;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setIntrinsic(final boolean value) {
|
public void setIntrinsic(final boolean value) {
|
||||||
|
this.intrinsic = value;
|
||||||
for (SpellAbility sa : this.abilities) {
|
for (SpellAbility sa : this.abilities) {
|
||||||
sa.setIntrinsic(value);
|
sa.setIntrinsic(value);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,11 @@ import forge.game.trigger.Trigger;
|
|||||||
|
|
||||||
public interface KeywordInterface extends Cloneable {
|
public interface KeywordInterface extends Cloneable {
|
||||||
|
|
||||||
|
Card getHostCard();
|
||||||
|
void setHostCard(final Card host);
|
||||||
|
boolean isIntrinsic();
|
||||||
|
void setIntrinsic(final boolean value);
|
||||||
|
|
||||||
String getOriginal();
|
String getOriginal();
|
||||||
|
|
||||||
Keyword getKeyword();
|
Keyword getKeyword();
|
||||||
@@ -34,8 +39,6 @@ public interface KeywordInterface extends Cloneable {
|
|||||||
void addSpellAbility(final SpellAbility s);
|
void addSpellAbility(final SpellAbility s);
|
||||||
void addStaticAbility(final StaticAbility st);
|
void addStaticAbility(final StaticAbility st);
|
||||||
|
|
||||||
void setHostCard(final Card host);
|
|
||||||
void setIntrinsic(final boolean value);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the triggers
|
* @return the triggers
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ import org.apache.commons.lang3.StringUtils;
|
|||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
import com.google.common.collect.Table;
|
|
||||||
|
|
||||||
import forge.game.CardTraitBase;
|
import forge.game.CardTraitBase;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
@@ -48,10 +47,7 @@ import forge.game.card.CardCollection;
|
|||||||
import forge.game.card.CardCollectionView;
|
import forge.game.card.CardCollectionView;
|
||||||
import forge.game.card.CardDamageMap;
|
import forge.game.card.CardDamageMap;
|
||||||
import forge.game.card.CardState;
|
import forge.game.card.CardState;
|
||||||
import forge.game.card.CardTraitChanges;
|
|
||||||
import forge.game.card.CardUtil;
|
import forge.game.card.CardUtil;
|
||||||
import forge.game.keyword.KeywordInterface;
|
|
||||||
import forge.game.keyword.KeywordsChange;
|
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.player.PlayerCollection;
|
import forge.game.player.PlayerCollection;
|
||||||
import forge.game.spellability.AbilitySub;
|
import forge.game.spellability.AbilitySub;
|
||||||
@@ -110,53 +106,6 @@ public class ReplacementHandler {
|
|||||||
game.getAction().checkStaticAbilities(false, Sets.newHashSet(affectedLKI), preList);
|
game.getAction().checkStaticAbilities(false, Sets.newHashSet(affectedLKI), preList);
|
||||||
checkAgain = true;
|
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);
|
runParams.put(AbilityKey.Affected, affectedLKI);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,6 +171,8 @@ public class ReplacementHandler {
|
|||||||
for (final ReplacementEffect re : affectedLKI.getReplacementEffects()) {
|
for (final ReplacementEffect re : affectedLKI.getReplacementEffects()) {
|
||||||
re.setHostCard(affectedCard);
|
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.Affected, affectedCard);
|
||||||
runParams.put(AbilityKey.NewCard, CardUtil.getLKICopy(affectedLKI));
|
runParams.put(AbilityKey.NewCard, CardUtil.getLKICopy(affectedLKI));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user