mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-20 20:58:03 +00:00
Fix Crawling Sensation + related tweaks (#4744)
* Fix Foretell rollback * Fix Crawling Sensation * Stonebinder's Familiar should trigger when Time Stop exiles spell card * Support Leave GY LKI * Tweak logic so it only reuses the table when simultaneous * Fix NPE * AI fix * Fix countered spell not exiled by Dauthi Voidwalker * Fix Parallax Wave not returning when exiling itself * Fix LKI update timing --------- Co-authored-by: TRT <> Co-authored-by: tool4EvEr <tool4EvEr@192.168.0.60>
This commit is contained in:
@@ -168,7 +168,7 @@ public class FightAi extends SpellAbilityAi {
|
|||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||||
AbilitySub tgtFight = sa.getSubAbility();
|
AbilitySub tgtFight = sa.getSubAbility();
|
||||||
while (tgtFight != null && tgtFight.getApi() != ApiType.Fight && tgtFight.getApi() != ApiType.DealDamage) {
|
while (tgtFight != null && tgtFight.getApi() != ApiType.Fight && tgtFight.getApi() != ApiType.DealDamage && tgtFight.getApi() != ApiType.EachDamage) {
|
||||||
// Search for the Fight/DealDamage subability (matters e.g. for Ent's Fury where the Fight SA is not an immediate child of Pump)
|
// Search for the Fight/DealDamage subability (matters e.g. for Ent's Fury where the Fight SA is not an immediate child of Pump)
|
||||||
tgtFight = tgtFight.getSubAbility();
|
tgtFight = tgtFight.getSubAbility();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1252,11 +1252,12 @@ public class GameAction {
|
|||||||
boolean orderedDesCreats = false;
|
boolean orderedDesCreats = false;
|
||||||
boolean orderedNoRegCreats = false;
|
boolean orderedNoRegCreats = false;
|
||||||
boolean orderedSacrificeList = false;
|
boolean orderedSacrificeList = false;
|
||||||
CardCollection cardsToUpdateLKI = new CardCollection();
|
|
||||||
|
|
||||||
for (int q = 0; q < 9; q++) {
|
for (int q = 0; q < 9; q++) {
|
||||||
checkStaticAbilities(false, affectedCards, CardCollection.EMPTY);
|
|
||||||
boolean checkAgain = false;
|
boolean checkAgain = false;
|
||||||
|
CardCollection cardsToUpdateLKI = new CardCollection();
|
||||||
|
|
||||||
|
checkStaticAbilities(false, affectedCards, CardCollection.EMPTY);
|
||||||
|
|
||||||
CardZoneTable table = new CardZoneTable(game.getLastStateBattlefield(), game.getLastStateGraveyard());
|
CardZoneTable table = new CardZoneTable(game.getLastStateBattlefield(), game.getLastStateGraveyard());
|
||||||
Map<AbilityKey, Object> mapParams = AbilityKey.newMap();
|
Map<AbilityKey, Object> mapParams = AbilityKey.newMap();
|
||||||
@@ -1445,10 +1446,14 @@ public class GameAction {
|
|||||||
|
|
||||||
table.triggerChangesZoneAll(game, null);
|
table.triggerChangesZoneAll(game, null);
|
||||||
|
|
||||||
if (!checkAgain) {
|
for (final Card c : cardsToUpdateLKI) {
|
||||||
break; // do not continue the loop
|
game.updateLastStateForCard(c);
|
||||||
} else {
|
}
|
||||||
|
|
||||||
|
if (checkAgain) {
|
||||||
performedSBA = true;
|
performedSBA = true;
|
||||||
|
} else {
|
||||||
|
break; // do not continue the loop
|
||||||
}
|
}
|
||||||
} // for q=0;q<9
|
} // for q=0;q<9
|
||||||
|
|
||||||
@@ -1473,10 +1478,6 @@ public class GameAction {
|
|||||||
// this point.
|
// this point.
|
||||||
checkStaticAbilities(false, affectedCards, CardCollection.EMPTY);
|
checkStaticAbilities(false, affectedCards, CardCollection.EMPTY);
|
||||||
|
|
||||||
for (final Card c : cardsToUpdateLKI) {
|
|
||||||
game.updateLastStateForCard(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!refreeze) {
|
if (!refreeze) {
|
||||||
game.getStack().unfreezeStack();
|
game.getStack().unfreezeStack();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -879,7 +879,7 @@ public final class GameActionUtil {
|
|||||||
oldCard.getZone().remove(oldCard);
|
oldCard.getZone().remove(oldCard);
|
||||||
// in some rare cases the old position no longer exists (Panglacial Wurm + Selvala)
|
// in some rare cases the old position no longer exists (Panglacial Wurm + Selvala)
|
||||||
Integer newPosition = zonePosition >= 0 ? Math.min(Integer.valueOf(zonePosition), fromZone.size()) : null;
|
Integer newPosition = zonePosition >= 0 ? Math.min(Integer.valueOf(zonePosition), fromZone.size()) : null;
|
||||||
fromZone.add(oldCard, newPosition);
|
fromZone.add(oldCard, newPosition, null, true);
|
||||||
ability.setHostCard(oldCard);
|
ability.setHostCard(oldCard);
|
||||||
ability.setXManaCostPaid(null);
|
ability.setXManaCostPaid(null);
|
||||||
ability.setSpendPhyrexianMana(false);
|
ability.setSpendPhyrexianMana(false);
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
package forge.game.ability;
|
package forge.game.ability;
|
||||||
|
|
||||||
|
import forge.game.Game;
|
||||||
import forge.game.GameEntity;
|
import forge.game.GameEntity;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardZoneTable;
|
import forge.game.card.CardZoneTable;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
|
||||||
import java.util.EnumMap;
|
import java.util.EnumMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -210,9 +212,14 @@ public enum AbilityKey {
|
|||||||
map.put(AbilityKey.LastStateGraveyard, table.getLastStateGraveyard());
|
map.put(AbilityKey.LastStateGraveyard, table.getLastStateGraveyard());
|
||||||
map.put(AbilityKey.InternalTriggerTable, table);
|
map.put(AbilityKey.InternalTriggerTable, table);
|
||||||
}
|
}
|
||||||
public static CardZoneTable addCardZoneTableParams(Map<AbilityKey, Object> map, forge.game.spellability.SpellAbility sa) {
|
public static CardZoneTable addCardZoneTableParams(Map<AbilityKey, Object> map, SpellAbility sa) {
|
||||||
CardZoneTable table = new CardZoneTable(sa.getLastStateBattlefield(), sa.getLastStateGraveyard());
|
CardZoneTable table = new CardZoneTable(sa.getLastStateBattlefield(), sa.getLastStateGraveyard());
|
||||||
addCardZoneTableParams(map, table);
|
addCardZoneTableParams(map, table);
|
||||||
return table;
|
return table;
|
||||||
}
|
}
|
||||||
|
public static CardZoneTable addCardZoneTableParams(Map<AbilityKey, Object> map, Game game) {
|
||||||
|
CardZoneTable table = new CardZoneTable(game.getLastStateBattlefield(), game.getLastStateGraveyard());
|
||||||
|
addCardZoneTableParams(map, table);
|
||||||
|
return table;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1595,9 +1595,6 @@ public class AbilityUtils {
|
|||||||
}
|
}
|
||||||
host.addRemembered(sb.toString());
|
host.addRemembered(sb.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sure that when this is from a trigger LKI is updated
|
|
||||||
host.getGame().updateLastStateForCard(host);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import forge.game.player.PlayerCollection;
|
|||||||
import forge.game.replacement.ReplacementEffect;
|
import forge.game.replacement.ReplacementEffect;
|
||||||
import forge.game.replacement.ReplacementHandler;
|
import forge.game.replacement.ReplacementHandler;
|
||||||
import forge.game.replacement.ReplacementLayer;
|
import forge.game.replacement.ReplacementLayer;
|
||||||
|
import forge.game.replacement.ReplacementType;
|
||||||
import forge.game.spellability.AbilitySub;
|
import forge.game.spellability.AbilitySub;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.trigger.Trigger;
|
import forge.game.trigger.Trigger;
|
||||||
@@ -945,11 +946,13 @@ public abstract class SpellAbilityEffect {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
public static void handleExiledWith(final Card movedCard, final SpellAbility cause) {
|
public static void handleExiledWith(final Card movedCard, final SpellAbility cause) {
|
||||||
|
handleExiledWith(movedCard, cause, cause.getHostCard());
|
||||||
|
}
|
||||||
|
public static void handleExiledWith(final Card movedCard, final SpellAbility cause, Card exilingSource) {
|
||||||
if (movedCard.isToken()) {
|
if (movedCard.isToken()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Card exilingSource = cause.getHostCard();
|
|
||||||
// during replacement LKI might be used
|
// during replacement LKI might be used
|
||||||
if (cause.isReplacementAbility() && exilingSource.isLKI()) {
|
if (cause.isReplacementAbility() && exilingSource.isLKI()) {
|
||||||
exilingSource = exilingSource.getGame().getCardState(exilingSource);
|
exilingSource = exilingSource.getGame().getCardState(exilingSource);
|
||||||
@@ -969,7 +972,9 @@ public abstract class SpellAbilityEffect {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public CardZoneTable getChangeZoneTable(SpellAbility sa, CardCollectionView lastStateBattlefield, CardCollectionView lastStateGraveyard) {
|
public CardZoneTable getChangeZoneTable(SpellAbility sa, CardCollectionView lastStateBattlefield, CardCollectionView lastStateGraveyard) {
|
||||||
if (sa.isReplacementAbility() && sa.getReplacingObject(AbilityKey.InternalTriggerTable) != null) {
|
if (sa.isReplacementAbility() && sa.getReplacementEffect().getMode() == ReplacementType.Moved
|
||||||
|
&& sa.getReplacingObject(AbilityKey.InternalTriggerTable) != null) {
|
||||||
|
// if a RE changes the destination zone try to make it simultaneous
|
||||||
return (CardZoneTable) sa.getReplacingObject(AbilityKey.InternalTriggerTable);
|
return (CardZoneTable) sa.getReplacingObject(AbilityKey.InternalTriggerTable);
|
||||||
}
|
}
|
||||||
return new CardZoneTable(lastStateBattlefield, lastStateGraveyard);
|
return new CardZoneTable(lastStateBattlefield, lastStateGraveyard);
|
||||||
|
|||||||
@@ -710,6 +710,11 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
|||||||
|
|
||||||
movedCard = game.getAction().moveTo(destination, gameCard, sa, moveParams);
|
movedCard = game.getAction().moveTo(destination, gameCard, sa, moveParams);
|
||||||
|
|
||||||
|
if (destination.equals(ZoneType.Exile) && lastStateBattlefield.contains(gameCard) && hostCard.equals(gameCard)) {
|
||||||
|
// support Parallax Wave returning itself
|
||||||
|
handleExiledWith(movedCard, sa, lastStateBattlefield.get(gameCard));
|
||||||
|
}
|
||||||
|
|
||||||
if (ZoneType.Hand.equals(destination) && ZoneType.Command.equals(originZone.getZoneType())) {
|
if (ZoneType.Hand.equals(destination) && ZoneType.Command.equals(originZone.getZoneType())) {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
sb.append(movedCard.getName()).append(" has moved from Command Zone to ").append(player).append("'s hand.");
|
sb.append(movedCard.getName()).append(" has moved from Command Zone to ").append(player).append("'s hand.");
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ public class ConniveEffect extends SpellAbilityEffect {
|
|||||||
CardCollection connivers = CardLists.filterControlledBy(toConnive, p);
|
CardCollection connivers = CardLists.filterControlledBy(toConnive, p);
|
||||||
while (!connivers.isEmpty()) {
|
while (!connivers.isEmpty()) {
|
||||||
GameEntityCounterTable table = new GameEntityCounterTable();
|
GameEntityCounterTable table = new GameEntityCounterTable();
|
||||||
final CardZoneTable triggerList = new CardZoneTable(sa.getLastStateBattlefield(), sa.getLastStateGraveyard());
|
final CardZoneTable triggerList = new CardZoneTable(game.copyLastStateBattlefield(), game.copyLastStateGraveyard());
|
||||||
Map<Player, CardCollectionView> discardedMap = Maps.newHashMap();
|
Map<Player, CardCollectionView> discardedMap = Maps.newHashMap();
|
||||||
Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
|
Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
|
||||||
AbilityKey.addCardZoneTableParams(moveParams, triggerList);
|
AbilityKey.addCardZoneTableParams(moveParams, triggerList);
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ public class CounterEffect extends SpellAbilityEffect {
|
|||||||
public void resolve(SpellAbility sa) {
|
public void resolve(SpellAbility sa) {
|
||||||
final Game game = sa.getActivatingPlayer().getGame();
|
final Game game = sa.getActivatingPlayer().getGame();
|
||||||
Map<AbilityKey, Object> params = AbilityKey.newMap();
|
Map<AbilityKey, Object> params = AbilityKey.newMap();
|
||||||
final CardZoneTable table = AbilityKey.addCardZoneTableParams(params, sa);
|
final CardZoneTable table = AbilityKey.addCardZoneTableParams(params, game);
|
||||||
|
|
||||||
for (final SpellAbility tgtSA : getTargetSpells(sa)) {
|
for (final SpellAbility tgtSA : getTargetSpells(sa)) {
|
||||||
final Card tgtSACard = tgtSA.getHostCard();
|
final Card tgtSACard = tgtSA.getHostCard();
|
||||||
|
|||||||
@@ -25,6 +25,9 @@ public class EndCombatPhaseEffect extends SpellAbilityEffect {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CR 721.2a
|
||||||
|
game.getTriggerHandler().clearWaitingTriggers();
|
||||||
|
|
||||||
// 1) All spells and abilities on the stack are exiled.
|
// 1) All spells and abilities on the stack are exiled.
|
||||||
Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
|
Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
|
||||||
CardZoneTable table = new CardZoneTable(sa.getLastStateBattlefield(), sa.getLastStateGraveyard());
|
CardZoneTable table = new CardZoneTable(sa.getLastStateBattlefield(), sa.getLastStateGraveyard());
|
||||||
@@ -35,7 +38,6 @@ public class EndCombatPhaseEffect extends SpellAbilityEffect {
|
|||||||
|
|
||||||
game.getStack().clear();
|
game.getStack().clear();
|
||||||
game.getStack().clearSimultaneousStack();
|
game.getStack().clearSimultaneousStack();
|
||||||
game.getTriggerHandler().clearWaitingTriggers();
|
|
||||||
|
|
||||||
// 2) State-based actions are checked. No player gets priority, and no
|
// 2) State-based actions are checked. No player gets priority, and no
|
||||||
// triggered abilities are put onto the stack.
|
// triggered abilities are put onto the stack.
|
||||||
@@ -43,7 +45,6 @@ public class EndCombatPhaseEffect extends SpellAbilityEffect {
|
|||||||
|
|
||||||
// 3) The current phase and step ends. The game skips straight to the postcombat main phase. As this happens,
|
// 3) The current phase and step ends. The game skips straight to the postcombat main phase. As this happens,
|
||||||
// all attacking and blocking creatures are removed from combat and effects that last “until end of combat” expire.
|
// all attacking and blocking creatures are removed from combat and effects that last “until end of combat” expire.
|
||||||
game.getPhaseHandler().endCombat();
|
|
||||||
game.getPhaseHandler().endCombatPhaseByEffect();
|
game.getPhaseHandler().endCombatPhaseByEffect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,11 @@ public class EndTurnEffect extends SpellAbilityEffect {
|
|||||||
if (sa.hasParam("Optional") && !ender.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantEndTurn"), null)) {
|
if (sa.hasParam("Optional") && !ender.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantEndTurn"), null)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Game game = ender.getGame();
|
Game game = ender.getGame();
|
||||||
|
// CR 721.1a
|
||||||
|
game.getTriggerHandler().clearWaitingTriggers();
|
||||||
|
|
||||||
// Steps taken from gatherer's rulings on Time Stop.
|
// Steps taken from gatherer's rulings on Time Stop.
|
||||||
// 1) All spells and abilities on the stack are exiled. This includes
|
// 1) All spells and abilities on the stack are exiled. This includes
|
||||||
// Time Stop, though it will continue to resolve. It also includes
|
// Time Stop, though it will continue to resolve. It also includes
|
||||||
@@ -42,7 +46,6 @@ public class EndTurnEffect extends SpellAbilityEffect {
|
|||||||
|
|
||||||
game.getStack().clear();
|
game.getStack().clear();
|
||||||
game.getStack().clearSimultaneousStack();
|
game.getStack().clearSimultaneousStack();
|
||||||
game.getTriggerHandler().clearWaitingTriggers();
|
|
||||||
|
|
||||||
// 2) All attacking and blocking creatures are removed from combat.
|
// 2) All attacking and blocking creatures are removed from combat.
|
||||||
game.getPhaseHandler().endCombat();
|
game.getPhaseHandler().endCombat();
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ public class HauntEffect extends SpellAbilityEffect {
|
|||||||
final Card copy = game.getAction().exile(card, sa, moveParams);
|
final Card copy = game.getAction().exile(card, sa, moveParams);
|
||||||
sa.getTargetCard().addHauntedBy(copy);
|
sa.getTargetCard().addHauntedBy(copy);
|
||||||
table.triggerChangesZoneAll(game, sa);
|
table.triggerChangesZoneAll(game, sa);
|
||||||
|
|
||||||
} else if (!sa.usesTargeting() && card.getHaunting() != null) {
|
} else if (!sa.usesTargeting() && card.getHaunting() != null) {
|
||||||
// unhaunt
|
// unhaunt
|
||||||
card.getHaunting().removeHauntedBy(card);
|
card.getHaunting().removeHauntedBy(card);
|
||||||
|
|||||||
@@ -1899,7 +1899,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public final void cleanupExiledWith() {
|
public final void cleanupExiledWith() {
|
||||||
if (exiledWith == null) {
|
if (exiledWith == null || exiledWith.isLKI()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import forge.game.CardTraitBase;
|
|||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.ability.AbilityKey;
|
import forge.game.ability.AbilityKey;
|
||||||
import forge.game.player.PlayerCollection;
|
import forge.game.player.PlayerCollection;
|
||||||
import forge.game.replacement.ReplacementType;
|
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.trigger.TriggerType;
|
import forge.game.trigger.TriggerType;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
@@ -85,7 +84,7 @@ public class CardZoneTable extends ForwardingTable<ZoneType, ZoneType, CardColle
|
|||||||
|
|
||||||
public void triggerChangesZoneAll(final Game game, final SpellAbility cause) {
|
public void triggerChangesZoneAll(final Game game, final SpellAbility cause) {
|
||||||
triggerTokenCreatedOnce(game);
|
triggerTokenCreatedOnce(game);
|
||||||
if (cause != null && cause.isReplacementAbility() && cause.getReplacementEffect().getMode() == ReplacementType.Moved) {
|
if (cause != null && cause.getReplacingObject(AbilityKey.InternalTriggerTable) == this) {
|
||||||
// will be handled by original "cause" instead
|
// will be handled by original "cause" instead
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -113,21 +112,25 @@ public class CardZoneTable extends ForwardingTable<ZoneType, ZoneType, CardColle
|
|||||||
|
|
||||||
public CardCollection filterCards(Iterable<ZoneType> origin, ZoneType destination, String valid, Card host, CardTraitBase sa) {
|
public CardCollection filterCards(Iterable<ZoneType> origin, ZoneType destination, String valid, Card host, CardTraitBase sa) {
|
||||||
CardCollection allCards = new CardCollection();
|
CardCollection allCards = new CardCollection();
|
||||||
if (destination != null) {
|
if (destination != null && !containsColumn(destination)) {
|
||||||
if (!containsColumn(destination)) {
|
return allCards;
|
||||||
return allCards;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (origin != null) {
|
if (origin != null) {
|
||||||
for (ZoneType z : origin) {
|
for (ZoneType z : origin) {
|
||||||
CardCollectionView lkiLookup = CardCollection.EMPTY;
|
|
||||||
if (z == ZoneType.Battlefield) {
|
|
||||||
lkiLookup = lastStateBattlefield;
|
|
||||||
}
|
|
||||||
if (containsRow(z)) {
|
if (containsRow(z)) {
|
||||||
|
CardCollectionView lkiLookup = CardCollection.EMPTY;
|
||||||
|
// CR 603.10a
|
||||||
|
if (z == ZoneType.Battlefield) {
|
||||||
|
lkiLookup = lastStateBattlefield;
|
||||||
|
}
|
||||||
|
if (z == ZoneType.Graveyard && destination == null) {
|
||||||
|
lkiLookup = lastStateGraveyard;
|
||||||
|
}
|
||||||
if (destination != null) {
|
if (destination != null) {
|
||||||
for (Card c : row(z).get(destination)) {
|
if (row(z).containsKey(destination)) {
|
||||||
allCards.add(lkiLookup.get(c));
|
for (Card c : row(z).get(destination)) {
|
||||||
|
allCards.add(lkiLookup.get(c));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (CardCollection cc : row(z).values()) {
|
for (CardCollection cc : row(z).values()) {
|
||||||
|
|||||||
@@ -1235,6 +1235,7 @@ public class PhaseHandler implements java.io.Serializable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public final void endCombatPhaseByEffect() {
|
public final void endCombatPhaseByEffect() {
|
||||||
|
endCombat();
|
||||||
game.getAction().checkStateEffects(true);
|
game.getAction().checkStateEffects(true);
|
||||||
setPhase(PhaseType.COMBAT_END);
|
setPhase(PhaseType.COMBAT_END);
|
||||||
advanceToNextPhase();
|
advanceToNextPhase();
|
||||||
@@ -1243,6 +1244,7 @@ public class PhaseHandler implements java.io.Serializable {
|
|||||||
public final void endTurnByEffect() {
|
public final void endTurnByEffect() {
|
||||||
extraPhases.clear();
|
extraPhases.clear();
|
||||||
setPhase(PhaseType.CLEANUP);
|
setPhase(PhaseType.CLEANUP);
|
||||||
|
game.fireEvent(new GameEventTurnPhase(playerTurn, phase, ""));
|
||||||
onPhaseBegin();
|
onPhaseBegin();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import forge.game.ability.AbilityKey;
|
|||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardCollection;
|
import forge.game.card.CardCollection;
|
||||||
|
import forge.game.card.CardUtil;
|
||||||
import forge.game.card.CardZoneTable;
|
import forge.game.card.CardZoneTable;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
@@ -31,6 +32,15 @@ public class TriggerChangesZoneAll extends Trigger {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hasParam("FirstTime")) {
|
||||||
|
// currently only for Crawling Sensation
|
||||||
|
List<Card> entered = CardUtil.getThisTurnEntered(ZoneType.smartValueOf(getParam("Destination")), null, getParam("ValidCards"), getHostCard(), this, getHostCard().getController());
|
||||||
|
entered.removeAll(filterCards(table));
|
||||||
|
if (!entered.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!matchesValidParam("ValidCause", runParams.get(AbilityKey.Cause))) {
|
if (!matchesValidParam("ValidCause", runParams.get(AbilityKey.Cause))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,6 +85,9 @@ public class Zone implements java.io.Serializable, Iterable<Card> {
|
|||||||
add(c, index, null);
|
add(c, index, null);
|
||||||
}
|
}
|
||||||
public void add(final Card c, Integer index, final Card latestState) {
|
public void add(final Card c, Integer index, final Card latestState) {
|
||||||
|
add(c, index, latestState, false);
|
||||||
|
}
|
||||||
|
public void add(final Card c, Integer index, final Card latestState, final boolean rollback) {
|
||||||
if (index != null && cardList.isEmpty() && index.intValue() > 0) {
|
if (index != null && cardList.isEmpty() && index.intValue() > 0) {
|
||||||
// something went wrong, most likely the method fired when the game was in an unexpected state
|
// something went wrong, most likely the method fired when the game was in an unexpected state
|
||||||
// (e.g. conceding during the mana payment prompt)
|
// (e.g. conceding during the mana payment prompt)
|
||||||
@@ -101,28 +104,30 @@ public class Zone implements java.io.Serializable, Iterable<Card> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Immutable cards are usually emblems and effects
|
if (!rollback) {
|
||||||
if (!c.isImmutable()) {
|
// Immutable cards are usually emblems and effects
|
||||||
final Zone oldZone = game.getZoneOf(c);
|
if (!c.isImmutable()) {
|
||||||
final ZoneType zt = oldZone == null ? ZoneType.Stack : oldZone.getZoneType();
|
final Zone oldZone = game.getZoneOf(c);
|
||||||
|
final ZoneType zt = oldZone == null ? ZoneType.Stack : oldZone.getZoneType();
|
||||||
|
|
||||||
// only if the zoneType differs from this
|
// only if the zoneType differs from this
|
||||||
// don't go in there is its a control change
|
// don't go in there is its a control change
|
||||||
if (zt != zoneType) {
|
if (zt != zoneType) {
|
||||||
c.setTurnInController(getPlayer());
|
c.setTurnInController(getPlayer());
|
||||||
c.setTurnInZone(game.getPhaseHandler().getTurn());
|
c.setTurnInZone(game.getPhaseHandler().getTurn());
|
||||||
if (latestState != null) {
|
if (latestState != null) {
|
||||||
cardsAddedThisTurn.add(zt, latestState);
|
cardsAddedThisTurn.add(zt, latestState);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (zoneType != ZoneType.Battlefield) {
|
if (zoneType != ZoneType.Battlefield) {
|
||||||
c.setTapped(false);
|
c.setTapped(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (zoneType == (ZoneType.Graveyard) && c.isPermanent() && !c.isToken()) {
|
if (zoneType == ZoneType.Graveyard && c.isPermanent() && !c.isToken()) {
|
||||||
c.getOwner().descend();
|
c.getOwner().descend();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
c.setZone(this);
|
c.setZone(this);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ ManaCost:2 G
|
|||||||
Types:Enchantment
|
Types:Enchantment
|
||||||
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | Execute$ TrigMill | TriggerZones$ Battlefield | TriggerDescription$ At the beginning of your upkeep, you may mill two cards.
|
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | Execute$ TrigMill | TriggerZones$ Battlefield | TriggerDescription$ At the beginning of your upkeep, you may mill two cards.
|
||||||
SVar:TrigMill:DB$ Mill | NumCards$ 2 | Defined$ You | Optional$ True
|
SVar:TrigMill:DB$ Mill | NumCards$ 2 | Defined$ You | Optional$ True
|
||||||
T:Mode$ ChangesZoneAll | ValidCards$ Land.YouOwn+nonToken | Origin$ Any | Destination$ Graveyard | TriggerZones$ Battlefield | ActivationLimit$ 1 | Execute$ TrigToken | TriggerDescription$ Whenever one or more land cards are put into your graveyard from anywhere for the first time each turn, create a 1/1 green Insect creature token.
|
T:Mode$ ChangesZoneAll | ValidCards$ Land.YouOwn+nonToken | Origin$ Any | Destination$ Graveyard | TriggerZones$ Battlefield | FirstTime$ True | Execute$ TrigToken | TriggerDescription$ Whenever one or more land cards are put into your graveyard from anywhere for the first time each turn, create a 1/1 green Insect creature token.
|
||||||
SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ g_1_1_insect | TokenOwner$ You
|
SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ g_1_1_insect | TokenOwner$ You
|
||||||
DeckHints:Ability$Delirium
|
DeckHints:Ability$Delirium
|
||||||
DeckHas:Ability$Graveyard
|
DeckHas:Ability$Graveyard
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
Name:Graceful Takedown
|
Name:Graceful Takedown
|
||||||
ManaCost:1 G
|
ManaCost:1 G
|
||||||
Types:Sorcery
|
Types:Sorcery
|
||||||
A:SP$ Pump | ValidTgts$ Creature.YouCtrl+enchanted | TargetMin$ 0 | TargetMax$ X | RememberObjects$ ThisTargetedCard | SubAbility$ DBPumpBis | SpellDescription$ Any number of target enchanted creatures you control and up to one other target creature you control each deal damage equal to their power to target creature you don't control.
|
A:SP$ Pump | ValidTgts$ Creature.YouCtrl+enchanted | TargetMin$ 0 | TargetMax$ X | AILogic$ PowerDmg | RememberObjects$ ThisTargetedCard | SubAbility$ DBPumpBis | SpellDescription$ Any number of target enchanted creatures you control and up to one other target creature you control each deal damage equal to their power to target creature you don't control.
|
||||||
SVar:DBPumpBis:DB$ Pump | ValidTgts$ Creature.YouCtrl | TargetUnique$ True | TargetMin$ 0 | TargetMax 1 | RememberObjects$ ThisTargetedCard | SubAbility$ DBEachDamage
|
SVar:DBPumpBis:DB$ Pump | ValidTgts$ Creature.YouCtrl | TargetUnique$ True | TargetMin$ 0 | TargetMax 1 | RememberObjects$ ThisTargetedCard | SubAbility$ DBEachDamage
|
||||||
SVar:X:Count$Valid Creature.YouCtrl+enchanted
|
SVar:X:Count$Valid Creature.YouCtrl+enchanted
|
||||||
SVar:DBEachDamage:DB$ EachDamage | ValidTgts$ Creature.YouDontCtrl | TgtPrompt$ Select target creature you don't control | DefinedDamagers$ Remembered | NumDmg$ Count$CardPower | StackDescription$ REP target creature_{c:ThisTargetedCard} | SubAbility$ DBCleanup
|
SVar:DBEachDamage:DB$ EachDamage | ValidTgts$ Creature.YouDontCtrl | TgtPrompt$ Select target creature you don't control | DefinedDamagers$ Remembered | NumDmg$ Count$CardPower | StackDescription$ REP target creature_{c:ThisTargetedCard} | SubAbility$ DBCleanup
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ A:SP$ ChooseCard | ValidTgts$ Creature | TgtPrompt$ Choose two target creatures
|
|||||||
SVar:DBUntap:DB$ Untap | Defined$ Targeted | SubAbility$ DBPutCounter | SpellDescription$ Untap them.
|
SVar:DBUntap:DB$ Untap | Defined$ Targeted | SubAbility$ DBPutCounter | SpellDescription$ Untap them.
|
||||||
SVar:DBPutCounter:DB$ PutCounter | Defined$ Targeted | CounterType$ P1P1 | CounterNum$ 2 | SubAbility$ DBPump | Put two +1/+1 counters on each of them.
|
SVar:DBPutCounter:DB$ PutCounter | Defined$ Targeted | CounterType$ P1P1 | CounterNum$ 2 | SubAbility$ DBPump | Put two +1/+1 counters on each of them.
|
||||||
SVar:DBPump:DB$ Pump | Defined$ Targeted | KW$ Vigilance & Indestructible & Haste | SubAbility$ DBAddCombat | SpellDescription$ They gain vigilance, indestructible, and haste until end of turn.
|
SVar:DBPump:DB$ Pump | Defined$ Targeted | KW$ Vigilance & Indestructible & Haste | SubAbility$ DBAddCombat | SpellDescription$ They gain vigilance, indestructible, and haste until end of turn.
|
||||||
SVar:DBAddCombat:DB$ AddPhase | ExtraPhase$ Combat | ExtraPhaseDelayedTrigger$ DelTrigStatic | ExtraPhaseDelayedTriggerExcute$ TrigEffect | SpellDescription$ After this main phase, there is an additional combat phase. Only the chosen creatures can attack during that combat phase.
|
SVar:DBAddCombat:DB$ AddPhase | ExtraPhase$ Combat | ExtraPhaseDelayedTrigger$ DelTrigStatic | ExtraPhaseDelayedTriggerExcute$ TrigEffect | ConditionPhases$ Main1,Main2 | SpellDescription$ After this main phase, there is an additional combat phase. Only the chosen creatures can attack during that combat phase.
|
||||||
SVar:DelTrigStatic:Mode$ Phase | Static$ True | Phase$ BeginCombat | TriggerDescription$ After this main phase, there is an additional combat phase. Only the chosen creatures can attack during that combat phase.
|
SVar:DelTrigStatic:Mode$ Phase | Static$ True | Phase$ BeginCombat | TriggerDescription$ After this main phase, there is an additional combat phase. Only the chosen creatures can attack during that combat phase.
|
||||||
SVar:TrigEffect:DB$ Effect | StaticAbilities$ ForbidAttack | Duration$ UntilEndOfCombat
|
SVar:TrigEffect:DB$ Effect | StaticAbilities$ ForbidAttack | Duration$ UntilEndOfCombat
|
||||||
SVar:ForbidAttack:Mode$ CantAttack | EffectZone$ Command | ValidCard$ Creature.YouCtrl+nonChosenCard | Description$ CARDNAME can't attack.
|
SVar:ForbidAttack:Mode$ CantAttack | EffectZone$ Command | ValidCard$ Creature.YouCtrl+nonChosenCard | Description$ CARDNAME can't attack.
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ Types:Artifact Creature Robot
|
|||||||
PT:2/2
|
PT:2/2
|
||||||
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigEnergy | TriggerDescription$ When CARDNAME enters the battlefield, you get {E}{E} (two energy counters).
|
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigEnergy | TriggerDescription$ When CARDNAME enters the battlefield, you get {E}{E} (two energy counters).
|
||||||
SVar:TrigEnergy:DB$ PutCounter | Defined$ You | CounterType$ ENERGY | CounterNum$ 2
|
SVar:TrigEnergy:DB$ PutCounter | Defined$ You | CounterType$ ENERGY | CounterNum$ 2
|
||||||
T:Mode$ Attacks | ValidCard$ Creature.Artifact+YouCtrl | Execute$ TrigPutCounter | TriggerDescription$ Whenever an artifact creature you control attacks, you may pay {E}. If you do, put your choice of a +1/+1, first strike, or trample counter on that creature.
|
T:Mode$ Attacks | ValidCard$ Creature.Artifact+YouCtrl | TriggerZones$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ Whenever an artifact creature you control attacks, you may pay {E}. If you do, put your choice of a +1/+1, first strike, or trample counter on that creature.
|
||||||
SVar:TrigPutCounter:AB$ PutCounter | Cost$ PayEnergy<1> | CounterType$ P1P1,First Strike,Trample | CounterNum$ 1 | Defined$ TriggeredAttackerLKICopy
|
SVar:TrigPutCounter:AB$ PutCounter | Cost$ PayEnergy<1> | CounterType$ P1P1,First Strike,Trample | CounterNum$ 1 | Defined$ TriggeredAttackerLKICopy
|
||||||
DeckHas:Ability$Counters
|
DeckHas:Ability$Counters
|
||||||
DeckHints:Type$Artifact
|
DeckHints:Type$Artifact
|
||||||
|
|||||||
@@ -2,6 +2,6 @@ Name:Warlord's Elite
|
|||||||
ManaCost:2 W
|
ManaCost:2 W
|
||||||
Types:Creature Human Soldier
|
Types:Creature Human Soldier
|
||||||
PT:4/4
|
PT:4/4
|
||||||
DeckHints:Type$Artifact
|
|
||||||
A:SP$ PermanentCreature | Cost$ 2 W tapXType<2/Artifact;Creature;Land/artifacts, creatures, and/or lands> | CostDesc$ As an additional cost to cast this spell, tap two untapped artifacts, creatures, and/or lands you control. | SpellDescription$ As an additional cost to cast this spell, tap two untapped artifacts, creatures, and/or lands you control.
|
A:SP$ PermanentCreature | Cost$ 2 W tapXType<2/Artifact;Creature;Land/artifacts, creatures, and/or lands> | CostDesc$ As an additional cost to cast this spell, tap two untapped artifacts, creatures, and/or lands you control. | SpellDescription$ As an additional cost to cast this spell, tap two untapped artifacts, creatures, and/or lands you control.
|
||||||
|
DeckHints:Type$Artifact
|
||||||
Oracle:As an additional cost to cast this spell, tap two untapped artifacts, creatures, and/or lands you control.
|
Oracle:As an additional cost to cast this spell, tap two untapped artifacts, creatures, and/or lands you control.
|
||||||
|
|||||||
Reference in New Issue
Block a user